F# 2.0, Silverlight 5, and Excel interop - com

Trying to do this:
let excel = AutomationFactory.CreateObject "Excel.Application"
excel?Visible <- true
excel?workbooks?Add ()
Although EXCEL.EXE does appear in the Task Manager when I run that code, the excel object apparently has no properties, which makes the rest of the code pretty tough to execute. (If I place let allProps = excel.GetType().GetProperties(BindingFlags.Public ||| BindingFlags.NonPublic) after the first line, it evaluates to an empty PropertyInfo array.)
In examples of Silverlight/Excel interop that I've seen, everyone simply does what I'm trying to do, with no problems. Not sure why I'm having this issue...??

AutomationFactory.CreateObject creates an automation server, which is intended to be used via dynamic dispatch. http://msdn.microsoft.com/en-us/library/ff457794(v=vs.95).aspx. To me, this makes sense, since the properties available would be defined by the object being automated.
Using object.GetType().GetProperties() will only give you pre-compiled properties of the object, not the dynamic properties.
If you are automating excel to create a spreadsheet, you may have an easier time using a third party library that does not use Excel automation. Many of them are listed in this answer: Create Excel (.XLS and .XLSX) file from C#

Related

VBA external modules

Is it possible to have modules be external to the actual Excel file and call the functions/subs within them externally?
My thinking is if there are multiple Excel files that use the same module, instead of updating each one of those files separately when I make a change, can I just update the one module stored on a server or something?
I have doing something like you describe for years. You can move your VBA code to a VB6 ActiveX dll, organize it into classes, and load that dll as a reference from Excel VBA.
This is a good way to reuse non-workbook specific code. For instance, I have code that queries a mainframe. I like to call it from Excel, but the details of the connection and how data is passed are contained in a dll that I can load from Excel, VB6, Word, .NET, wherever. I have a similar dll for reading data from AutoCAD drawings, one for interfacing with a product DB on a MySQL server, etc.
The code that remains in Excel tends to be simple formatting stuff. Say I return a variant array of strings (technically a COM SAFEARRAY) from some library that I wrote. I would then output it into Excel, maybe do a text-to-columns, and have a list of results returned to the user.
You can also pass and return more complex data structures. The beauty of VB6/COM Automation (and I didn't appreciate this until I learned to do it the harder way in VB.NET or C#) is that the data will flow in and out of your components seamlessly and all the necessary interfaces will be created for you.
The main change to your code will be replacing things like ThisWorkbook or ActiveSheet with explicit parameters like (Byval sht as Excel.Worksheet). These will be caught at compile time in VB6 (since it doesn't know what ThisWorkbook is), so you cannot overlook them; you are forced to pass an explicit reference.
I also notice that my code inside the dll becomes more paranoid if it receives a Worksheet or other Excel object as a parameter. In VBA you might have had more assurance that you were passing a good object since it was private to a given workbook. The dll does not know who is calling it, so I view the passed-in object with more suspicion (check if Nothing, sheet name, formatting clues to ensure I am using what I think I am using).
The only downside I see is that you will have to get a copy of Visual Basic 6.0. I bought mine in 1998. It is no longer available from Microsoft, but surely there is someone out there who will sell it to you. The latest service pack is SP6.
You will also have to become familiar with "regsvr32" and "regsvr32 /u" to deal with the "ActiveX can't create component" errors as you open your workbooks on various computers. I just publish my dlls to a mapped network drive and re-register them on the user's computers whenever there is a significant change. Obviously this is a intranet/single company solution. Publishing the updated versions is much more of a pain the farther you are distributed.
Not sure if this would satisfy your needs, but you could create your common module as an "add-in" and so install it so that all files that you open in the same instance of excel would have access to the add-in code.
It would not be my recommended way of doing it because I would be worried about suitable testing of all the excel files that use it, when you make a change, plus the added complexity of getting users to install your add-in (this may not be an issue for you). I have a "developersToolkit" module I use across 8 different Workbooks, but I import the module into each workbook so its stand alone and I can also test changes for compatibility with each of the 8 workbooks.

VBA and late binding: Where do I find the numeric equivalents to constants?

I'm a complete VBA newbie, having decided to teach myself over a weekend, so forgive the stupid question(s). I'm trying to automate some routine tasks involving generating Word documents or emails from an Excel Spreadsheet. Because there will be multiple software versions involved, I am using late binding to open Word and Outlook. My question is: Where can I find a simple reference telling me what the index numbers are that correspond to the application constants? I have killed a lot of time googling to learn that, for example, the Outlook foldertype for "Contacts" is "10". Maybe someone knows of a web link that could save me countless hours of searching?
Update: http://msdn.microsoft.com/en-us/library/office/gg278936%28v=office.14%29.aspx seems to have some of the information I need, although it's not always intuitive where the information is. For example, if it contains the outlook folder type constants, I haven't found them yet.
See here
Enumeration http://msdn.microsoft.com/en-us/library/office/ff860961(v=office.15).aspx
OlDefaultFolders Enumeration http://msdn.microsoft.com/en-us/library/office/ff861868(v=office.15).aspx
I would recommend to add the relevant object libraries to your project as References during development time. You do this by using the Tools - References Menu in the VBA Editor. This makes developing a lot easier as you can use intellisense while writing the code.
If you need only a few Enums or single Constants in your code the easiest way to get their values is to hit [F2] in in VBA Editor while the object libraries are still referenced. Then search for the constants name and copy its value to your code.
Just using the numeric values of the constants in your code makes the code pretty hard to read. So I would recommend to re-declare all the Enums/Constants you actually use in a module in your own project. That massively improves the readability of your code.
So, instead of just copying the value from the VBA Object Browser, I suggest you copy the name and the value and put it your own code as a constant declaration. For your example of the Outlook contacts folder this will look like this:
Public Const olFolderContacts = 10
You can then use the constant in your procedures as you would do with Early Binding.
Should you work on a larger automation project using many of the constants from any one of the Office Object Libraries, you can download ready-made VBA modules containing all the Office constants from my website. You can then just import the relevant modules into your project and are ready to go.
After you finished the main development work, you remove the linked libraries from your project and declare the relevant object variables As Object instead of the actual type.
Always remember to compile your project not to miss any declaration that does not work late binding.

"run-time error '453' can't find dll entry point" from vb.net compiled dll referenced by vba

I am coding in vb.net through visual studio 2008. I have successfully compiled a dll file from my code but I continue to get "Run-time error '453'" when I try to reference the dll in vba. I understand there is some sort of compiling error that occurs when using vb.net. Does anyone have any suggestions to fix/overcome this issue? I would like to avoid translating the code to another language as much as possible.
Here is a simple sample code that I have been trying to get functioning:
Example.dll:
Public Class Class1
Function Square(ByVal x As Double, ByRef y As Double)
y = x * x
Return 0
End Function
End Class
Macro in Example.xlsx:
Private Declare Function Square Lib "\Example.dll" (ByRef x As Double, ByRef y As Double)
Sub Test()
Dim x, y As Double
x = 2
y = 0
Call Square(x, y)
MsgBox (y)
End Sub
Thank you,
Katlynn
Ok, a truckload of issues here and assumptions are incorrect.
First up, when you use declare in VBA to a .dll, you cannot use relative path names.
So you have:
Private Declare Function Square Lib
"\Example.dll" (ByRef x As Double, ByRef y As Double)
You can’t use \Example.dll
You have to specify the FULL path name. Eg:
C:\mytestApp\Example.dll
So even if vb.net did produce a working .dll, you have to use a full path name – no relative path name is allowed.
You can place the .dll in the standard windows path names that are searched for .dll’s (so c:\windows\system32), and thus NOT have to use a full path name.
However, these days with security, adding or just tossing a .dll into a windows system folder tends to have a LOT of security issues and HUGE hassles. (windows makes this hard, and so does any virus software). So you have to use a full path name.
Next up:
Using declare in VBA is a means to call the windows API, or what we call windows x32 bit library code. This is not COM (ActiveX), but has to be raw windows x32 (or x64) native windows code.
So this is not a ActiveX, or “com” object, but raw code that you call. A standard vb.net (or any .net language) does NOT produce windows native code. The correct term is
Managed code = .net code. This code is NOT native windows code.
Un-managed code = raw windows code.
So you have to create un-managed code to use DECLARE in VBA.
So you have to use:
X86 assembler
C++
VB6
Or ANY development tool that can produce raw x32 (or if using access x64, then raw x64 native windows code).
In visual studio these days you "can" produce native windows code, but you have to use un-managed code, and that thus means c++. (c#, or vb.net don't produce native code or dll's).
So you can use c++, or VB6 or even a assembler to create these .dll’s.
Such .dlls do NOT require registration. And as noted you don’t create an “instance” of the object in VBA code, but call it directly.
These types of .dll’s (simple external library code calls) thus do not appear as a COM object. And if you in VBA editor go tools->references, then you not see these libraries appear. However, they can be ease to use, since you don't have to create a instance of the object I VBA code - you just call such code like your example.
Because the path name is hard coded with this approach, then it often a pain to use the DECLARE statement (since it is determined at VBA compile time, and not runtime). This would mean you have to place that .dll in the SAME location for each computer you deploy to. If you move or change the folder, then your code would fail.
To get around this issue, you can use the LOADLIBARY call in VBA. This will allow you at runtime to set (determine) the location of the .dll. So most developers will thus simple place the .dll in the SAME folder as the running application (same folder as the Access or in this case Excel file).
However, this assumes you create a standard windows .dll.
Can I create such native windows .dll’s in .net with vb.net?
It turns out you can with some external add-in’s for VS. I often use this approach to create these dll’s, since then I don’t have to build COM interface code, and it is VERY simple to use.
I then combine the above with VBA using a LOADLIBRARY call, and that works quite well. So in VS you can use a NuGet package (add in) and pick a .dll export add-in utility. There are several, but I used the RGeseke DLLimport with great success. These are free 3rd party choices here.
While the above reduces some hassles, you will have to adopt the VBA loadlibrary code to make this a practical choice (or always place the .dll in the same location). I also recommend this approach if you have some existing .dll’s, and want to interface to that code but don’t want to learn say c++. So I do use this approach to consume existing dll’s in vb.net, and thus in turn from Access. In effect this trick allows you to write interfaces to windows .dll code in a nice familiar language like vb.net.
However, if you not used LOADlibrary from VBA (and VB6) in the past, and are not up to speed, then I will suggest you choose the next option.
Creating COM (ActiveX) from vb.net
This is likely the best approach. And it is the most common approach. However, you have to trade off having to create a COM object in vb.net, and THEN register it on the target computer.
So, the only hassle is this requirement to register the .dll on the target computer.
Having to register (regasm.exe) the COM object on the target machine often requires elevated rights. And with companies so security conscious these days, then this installing requirement can be a hassle, but it still likely the best option.
Here is a working example of your code in vb.net.
Imports System.Runtime.InteropServices
<ClassInterface(ClassInterfaceType.AutoDual)>
Public Class Class1
Function Square(ByVal x As Double) As Double
Dim y As Double
y = x * x
Return y
End Function
End Class
Note the above including of interop, and the autoDuel setting in code. This is what allows .net to create a standard windows COM object interface that can be consumed by VBA, VB6, windows scripting, or just any platform that supports COM objects.
Settings you need for this to work:
FORCE the .net project to x86. (you using Excel x32).
So in VS go build->configuration manager. Click on platform in the grid, and choose new, choose x86 and click ok.
You should now have this:
Next up, ensure that the project is made com visible. This check box is USUALLY checked for you by default. All .net classes tend to be marked this way. But I seen it become un-checked when you mess with the previous step of changing the build to x86. So do a quick look at this setting.
Project->properties
Now on Application area, click on assembly information box.
You need to ensure this check box is set:
So above takes only a few seconds.
So check the above (they are usually are already set for you).
Next, we have to tell VS to register this COM object on our development computer. (this is that regasm.exe requirement).
So, in compile area, check this box:
Now, the above check box in VS is ONLY for your development computer and convenience. It simply does a regasm for you, and this is the magic that exposes the class as a COM object for use in VBA or ANY system that supports COM.
Note that the above check box is ONLY for your convenience on your development machine. It does NOT change, modify or do ANYTHING to the code or project.
Now to deploy this code to other machines, you have to provide a batch file or some other means to execute the regasm.exe command. As I pointed out, these does make deployment a greater hassle then the .dll approach, but you trade off other ease of coding here.
As this point, compile (build) the above.
Once you done so, you find now in the VBA editor, you can go tools->references and you find your project as a registered COM object for consuming in VBA.
You see this: (my project was call Testcom2.
And note that once you do set this reference, you MUST EXIT excel if you make changes or re-compile the .net project. (because EXCEL will have locked and have that .dll in use once you set this reference). So just remember if you going to re-compile the .net code, make sure you exit Excel.
And I changed you code a bit, since a function returns a value, and you always returning 0. You could use a call and use a Sub if you wanted, but you HAVE function defined in vb.net and did not define a sub.
So in VBA, we now have this:
Sub TEst55()
Dim cMySquare As New TestCom2.Class1
Dim a As Double
Dim b As Double
a = 10
b = cMySquare.Square(a)
Debug.Print b
End Sub
Note how even intel-sense works as I type, so the function show up as a method in VBA.
And even the types and parms show up while you type in VBA such as:
And hitting f5 in VBA to run the code, we get this as output:
100
Its been a while since I've done this so I'm not sure if its necessary anymore but did you try adding all of the COM attributes?
http://support.microsoft.com/Default.aspx?kbid=817248

How do I add a "browse for file" dialog box to the transferText command?

I'm creating a macro in MS Access that imports a CSV file into a table. I'm using the TransferText action to import the CSV string, and I want to allow the user to browse for the file that contains the CSV string.
How do I show the "browse" dialog box to enable the user to choose the file?
See API: Call the standard Windows File Open/Save dialog box. However this requires VBA code.
An alternative (which I would say is second choice to the API call recommended by #Tony Toews and #draice) would be to use the Application.FileDialog object. This has been part of the Office automation library for as long as VBA has been in Access, but in recent versions of Access (starting with either A2002 or A2003, I don't know which), the top-level Access Application has provided a wrapper for this object. Beware, though, that without a reference to the Office automation library, the ENUM values that show up in Intellisense can't be used without a reference (a helpful error message informs you of this and offers to create the reference). In short, if you use it's best to use it as you would any automation object with late binding, with the exception that you don't have to initialize the top-level object with Application.CreateObject, as it's already there for you to use.
EDIT:
#draice asks:
I don't understand the following
statements that you wrote: "the
top-level Access Application has
provided a wrapper for this object"
"it's best to use it as you would any
automation object with late binding"
Tony's API code will work in every version of Windows, and because MS believes in backward compatibility, they will never break this API call in future versions of Windows.
the FileDialog object is not easy to use in VBA unless you add the reference to the Office Automation library. It is better to minimize the number of references in your Access database, because all sorts of things can mess them up and cause your app to break (any missing reference will prevent all VBA code from running). In order to avoid problems from missing references, we use late binding so that the code you write is not dependent on the outside libary.
Microsoft might remove this object from future versions of Access. The FileSearch object is an analogous situation, in that it was introduced in A95/97 as part of the Office Automation library, and then a wrapper around it was created in A2000, but Microsoft removed it in A2007 (and provided no alternative at all). MS could choose to remove the FileDialog object in future versions of Access and then your code would break. But the API call is never going to break.
Answer can be found at http://www.access-programmers.co.uk/forums/showthread.php?p=917371#post917371.

Include sql scripts in a VB6 application

I am maintaining an old VB6 application, and would like to include SQL scripts directly in part of the project. The VB6 application should then extract the text of this script and execute it on the server.
The reasons for this approach are various - among others, we want to deliver only an updated executable rather than a complete update/installation package. Hence, the SQL scripts need to be compiled into the application just like a resource file. And, obviously, one has to be able to get at the content from code, in order to send it to the database server.
Does anyone have a good way to do this?
The simplest solution is to just create a VB module with the scripts as strings.
If you want to use a resource file instead, you can do that too. You can associate a resfile with a VB project (I don't remember how to do this directly in the VB IDE but the VBP file supports a ResFile32 parameter).
EDIT: It seems like the issue here is mostly about formatting -- you don't want to store SQL queries as one long string, but formatting the query nicely inside VB is tedious because you have to add quotes, add string concatenation operators to join the lines together, etc.
I would recommend placing the SQL in a text file and formatting it in whatever way you like. Write a script that will take the text and convert it into a VB module. The build process would be modified to always apply this script first before compiling the application.
For scripting, use your favorite scripting language; if you don't have a favorite scripting language, this is an easy enough task that you could do it in VB, C#, or any other language. If it were me, I'd probably use awk (gawk) or Python.
If you want to use a resource (.RES) to store your SQL, go to the menu:
Add-ins > Add-in Manager...
and select VB 6 Resource Editor. Configure the add-in to be loaded and to load at startup.
From the editor add-in, VB provides a simple interface to add resource strings. You will refer to these using the provided constant values. To load the strings at runtime, use the LoadResString function:
Public Const SQL_INSERT As Integer = 101
Dim strSQL As String
strSQL = LoadResString(SQL_INSERT)
(replace "101" with the constant value of the string you wish to load)
Just another thought on your approach. Because I find myself tweaking the program's behavior or UI for customers I might be in the middle of a change that either is not ready or has not yet been tested and approved. So if I have properties that change from time to time, but I want to maintain control of, for instance connection settings to our ftp server, I will create a resource only dll exposing my properties and use a resource file in the dll to supply the values. When my network manager changes something on the ftp server I change the strings in the resource maanger, recompile the dll and release just the updated dll. I'm sure there are many more solutions, but that is how I do it. If you don't think you might have to change your SQL scripts at the same time you are changing you exe this probably only complicates your work. It has worked well enough for me that now this is pretty much standard for me.