How do I read the version information (Major, Minor and Revision values) of a COM library (.dll)?
Both the executable and the library are Visual Basic 6 projects.
If you have control of the DLL's source code (you wrote it, for example), you can add methods to expose the App.Major, App.Minor, and App.Revision properties. In your DLL code, add this:
Public Function Major() As String
Major = App.Major
End Function
In your code that uses the DLL, then:
Public Sub Whatever
With New myClass
MsgBox .Major
End With
End Sub
Make analogous methods for Minor and Revision, and there you are. If you don't have the source code, you have to hope that whoever wrote the DLL did this, or you may have to resort to trying to find something obscure in the link that Lynn shared.
Related
So this is my first post in this great community and I'm an absolute beginner, I'm sure I'll get good advice from here.
I mad this simple VB class library in Visual Studio 2017
Public Class Addition
Public Function Add(No1 As Long, No2 As Long) As Long
Return No1 + No2
End Function
End Class
I made it COM visible and checked Register for COM interop. and build the project.
In my access VBA project, I've added the reference to my Dll without a problem and put the following code behind the click event of a button.
Private Sub Command0_Click()
Dim myCalc As ShwDolphin.Addition
Set myCalc = New ShwDolphin.Addition
Dim Num As Long
Num = myCalc.Add(2, 5)
Debug.Print Num
End Sub
"ShwDolphin" is VB assembly name.
But I always get this error message
"Runtime error 429 Active X component can't create an object"
Can you please tell me what I'm doing wrong here?
This is driving me crazy.
Thank you very much.
Reading the comments, my guess is that you have a difference in what I call "bitness". Default projects in Visual Studio are generally 32-bits. If you build a 32-bit COM DLL, it can only be loaded by 32-bit processes. If your Office installation is 64-bit, it will never work. What you will need to do is build/register it for 64-bits.
Now, if you build for MSIL and not any specific processor (x86 or x64), you don't have to really rebuild for 64-bits. All that is necessary is to register for 64-bits. (The default build configuration is MSIL.)
So you need to use the 64-bit regasm in C:\windows\Microsoft.NET\Framework64\v4.0.30319\regasm.exe
use that regasm with options of /tlb /codebase "thenameofyour.dll"
If you built x64 and Office is 32-bit, then do the opposite: use the 32-bit regasm, but odds are that you are using defaults which is 32-bit.
You can also look at this answer:activex can't create object by vbs but can by classic asp
So after much frustration, I was able to make it work by doing the following:
1- Make a new project with the same code (It was easier than removing previous dlls).
2- Disable "Register for COM interop" option in project properties of Visual Studio.
3- Build project.
4- Use use the 64-bit regasm in C:\windows\Microsoft.NET\Framework64\v4.0.30319\regasm.exe to register my assembly.
5- Add reference to the generated tlb file in the VBA editor.
Thanks to everyone for your generous help, thanks to this community.
I don't know why I had to do this way, but I'm using 64-bit windows and 64-bit office.
Also like #dee said adding interface was not necessary, it just worked.
To be able to use custom .NET class in VBA, a *.dll must expose methods (and properties) to COM automation. What this means to you? You have to create an interface.
Public Interface IAddition
Function Add(No1 As Long, No2 As Long) As Long
End Interface
Public Class Addition
Implements IAddition
Public Function Add(No1 As Long, No2 As Long) As Long Implements IAddition.Add
Return No1 + No2
End Function
End Class
Usage:
Private Sub Command0_Click()
Dim myCalc As ShwDolphin.IAddition
Set myCalc = New ShwDolphin.Addition
Dim Num As Long
Num = myCalc.Add(2, 5)
Debug.Print Num
End Sub
For further details, follow the instruction provided in my past answer: Generics and Com Visible .NET libraries
You might be interested in this article too: Extend your VBA code with C#, VB.NET, or C++/CLI
Good luck!
I have one VB6 ActiveX DLL that exposes a class INewReport. I added some new methods to this class and I was able to rebuild it and keep binary compatibility.
I have a second DLL that exposes a class clsNewReport, which implements the first class using:
Implements RSInterfaces.INewReport
Since I added new methods to INewReport, I had to also add those new methods to clsNewReport.
However, when I try to compile the second DLL, I get the binary-compatibility error "...class implemented an interface in the version-compatible component, but not in the current project".
I'm not sure what is happening here. Since I'm only adding to the class, why can't I maintain binary compatibility with the second DLL? Is there any way around this?
I think this is a correct explanation of what is happening, and some potential workarounds.
I made up a test case which reproduced the problem in the description and then dumped the IDL using OLEView from the old & new DLL which contained the interface.
Here is a diff of the old (left) and new IDL from INewReport:
Important differences:
The UUID of interface _INewReport has changed
A typedef called INewReport___v0 has been added which refers to the original UUID of the interface
(I assume that this is also what is happening to the code referred to in the question.)
So now in the client project the bincomp DLL refers to the original interface UUID; but that UUID only matches against a different name (INewReport___v0 instead of INewReport) than it did originally. I think this is the reason VB6 thinks there is a bincomp mismatch.
How to fix this problem? I've not been able to do anything in VB6 that would allow you to use the updated interface DLL with the client code without having to break bincomp of the client code.
A (bad) option could be to just change the client DLL to use project compatibility... but that may or may not be acceptable in your circumstances. It could cause whatever uses the client DLL to break unless all the consumers were also recompiled. (And this could potentially cause a cascade of broken bincomp).
A better but more complex option would be to define the interface in IDL itself, use the MIDL compiler to generate a typelib (TLB file), and reference that directly. Then you would have full control over the interface naming, etc. You could use the IDL generated from OLEView as a starting point for doing this.
This second option assumes that the interface class is really truly an interface only and has no functional code in it.
Here's how I setup a case to reproduce this:
Step 1. Original interface definition - class called INewReport set to binary compatible:
Sub ProcA()
End Sub
Sub ProcB()
End Sub
Step 2. Create a test client DLL which implements INewReport, also set to binary compatible:
Implements INewReport
Sub INewReport_ProcA()
End Sub
Sub INewReport_ProcB()
End Sub
Step 3: Add ProcC to INewReport and recompile (which also registers the newly built DLL):
(above code, plus:)
Sub ProcC()
End Sub
Step 4: Try to run or compile the test client DLL - instantly get the OP's error. No need to change any references or anything at all.
I was able to recreate your problem, using something similar to DaveInCaz's code. I tried a number of things to fix it, probably repeating things you've already tried. I came up with a possible hypothesis as to why this is happening. It doesn't fix the problem, but it may throw some additional light on it.
Quoting from This doc page:
To ensure compatibility, Visual Basic places certain restrictions on changes you make to default interfaces. Visual Basic allows you to add new classes, and to enhance the default interface of any existing class by adding properties and methods. Removing classes, properties, or methods, or changing the arguments of existing properties or methods, will cause Visual Basic to issue incompatibility warnings.
Another quote:
The ActiveX rule you must follow to ensure compatibility with multiple interfaces is simple: once an interface is in use, it can never be changed. The interface ID of a standard interface is fixed by the type library that defines the interface.
So, here's a hypothesis. The first quote mentions the default interface, which suggests that it may not be possible to alter custom interfaces in any way. That's suggested by the second quote as well. You're able to alter the interface class, because you are essentially altering its default interface. However, when you attempt to alter the implementing class in kind, to reflect the changes in your interface, your implementation reference is pointing to the older version of the interface, which no longer exists. Of course, the error message doesn't hint at this at all, because it appears to be based on the idea that you didn't attempt to implement the interface.
I haven't been able to prove this, but looking at DaveInCaz's answer, the fact that the UUID has changed seems to bear this idea out.
I've been having some trouble with this topic for awhile and thought I'd ask the community for advice. I just can't seem to find anything geared towards vb.net online, and my C# knowledge is not sharp or complete enough to make sense of the C# examples.
I'd like to be able to create a class library in vb.net for use in VBA macros elsewhere. In particular, in Excel and a terminal emulator we use. I'm trying to start simple, and work my way up from there. For the moment, here is the test code I'm trying to use:
Public Class ComTest
Public Function SayHello() As String
Return "Hello"
End Function
End Class
Under properties > application > assembly information I've checked the "make assembly com visible" box.
When I try to set a reference to this dll using the VBA editor I get the error message that I can't add a reference to the specified file. If I try to declare the function as such:
Private Declare Function SayHello Lib "C:\SomePath\ComTest.dll" ()
I get a Run-time error '453': Can't find DLL entry point SayHello in C:\SomePath\ComTest.dll
I vaguely understand that declaring the function would be the approach to take with a "regular" DLL containing unmanaged code. The former would actually be creating a COM component. Given the choice, I'd take the former and just set a reference to the file. In particular, I'd like it to be registration free, but I think I understand that part if I can just get it to work as a com component for VBA.
So if anyone can provide some advice on either method, I'd be most thankful.
You shouldn't declare a function when you use COM. COM was made so that you wouldn't need to do that.
Your application should have an assembly name in the property pages (in the same dialog where you've set the "make assembly com visible" check box). That name is used to instantiate your object with:
Dim obj, msg
Set obj = CreateObject("YourAssemblyName.ComTest")
msg = obj.SayHello()
If you need to early bind your library, just add it to your project references (it should appear listed in the components list). Then you can do:
Dim obj As YourAssemblyName.ComTest
Dim msg As String
Set obj = New YourAssemblyName.ComTest
msg = obj.SayHello()
We have developed project using vb.net for our internal purposes and then we obfuscated it. It is throwing error as mentioned below.
“Public member ‘Var1’ on type ‘e’ not found.”
Code:
Public Sub get_constants_from_DbList(ByRef frm As Object, ByRef sDbname As String)
For Each Row As DataRow In CommonObj.DSCommonProc.Tables("dblist").Rows
If StrComp(Row("DbName").ToString, sDbname, CompareMethod.Text) = 0 Then
prg_id = Row("PrgId").ToString
frm.Var1= Row("ChangesDbName").ToString
frm.Var2 = Row("LoadTableName").ToString
frm.Var3 = Row("ServerName").ToString
Exit Sub
End If
Next
End Sub
A form (named FrmMain) is passed to the parameter ‘frm’ from the calling procedure to this sub-routine. Var1, etc are public variables in that form.
Obfuscation tools we tried are –
SmartAssembly 6
PreEmptive Dotfuscator and Analytics CE (which has come with Visual studio 2012)
Without obfuscation exe is working fine.
Error is thrown while assigning variable ‘Var1’ in the sub-routine. If the code line is modified as below then obfuscated exe will work fine.
FrmMain.Var1= Row("ChangesDbName").ToString
We thought obfuscation is missing late binding & tried similar type of code in a small sample project. But that didn’t have any error. We have attached this small code. But due to its magnitude we can’t upload original project.
How can we trace the error?
You can find the source code of my sample application here
Don't use obfuscation with reflection/late binding/dynamic. It will only get you into troubles like this.
The obfuscator will obfuscate all private & internal identifiers but it can't know that you are binding to them by name at run-time.
Turn on Option Strict and resolve the errors (i.e. change the type of the argument frm to its real type)
Obfuscators rely on static analysis to determine what is "safe" to obfuscate. When you introduce late binding and reflection into the mix, it becomes very difficult to detect that something isn't safe to rename. Most obfuscators provide the ability to exclude certain elements of your application from obfuscation so that you can work around this problem.
I actually don't know VB.Net very well, but with the way that you're doing late binding appears to be something that an obfuscator can't detect. So, this means you need to exclude that property from being renamed. In Dotfuscator at least, this is should be easy. You should also be able to turn on "Library Mode" which will automatically exclude all public members of every class.
Each of my VB.NET projects needs a certain set of custom modules.
For example:
modLog
modGUID
modControls
modRegistry
In SOME of these modules I have a few references to other modules.
For example the sub modLog.WriteLog goes like this:
Public Sub WriteLog(Byval uText As String)
If globalclassOEM.IsOEMVersion Then
sPath = (some custom path)
Else
sPath = (some default path)
End if
'Write log text to file
End Sub
This is really stupid because sometimes I have to add really many modules and classes to a tiny projects just to solve some dependencies as the above and to be able to use a few modules that I really need.
Is there any best tactics in VB.NET to avoid such situations?
The best way to avoid such problems, would be to avoid that problem ;) Means: Libraries should do exactly what they are meant to do and not do some "extra work" in the backgorund. In your example: Why does the WriteLog function need to determine the path and why doesnt the caller define it and pass it to the logging function/class?
IF you still want or need to have the functions in that way, you might circumvent the problem by defining interfaces and then put ALL your interfaces into a single library, but NOT the classes that implement them. That would still require to load the file with the interface definitions, but of course you don't need to load any class that implements it.
You might also use some kind of plugin system and when your logging class (in this example) is created, it might try to dynamically load the required assemblies. If they do not exit, the class will without them, otherwise it can use them as pretended. Doesnt make programmers life easier, though (imho).
The "best" way (imho again) would be the first suggestion. Dont have "low level" libraries referencing other libraries. Everything else most likely would be considered to be a design flaw and not a "solution".
I have not covered a whole heap of referencing in VB.net, however, would it be possible for you to create a .dll with all the base modules. This would mean you could reference this .dll saving you time. For the extenuating circumstances where you have references to other modules you could just manually write that module.
As others have alluded to, you never want to directly include the same code file in multiple projects. That is almost never a good idea and it always leads to messy code. If you have common code that you want to share between two different projects, then you need to create a third project (a class library) which will contain the common code, and then the other two projects will just reference to the class library. It's best if you can have all three projects in the same solution and then you can use project references. However, if you can't do that, you can still have a direct file reference to the DLL that is output by that class library project.
Secondly, if you really want to avoid spaghetti code, you should seriously look into dependency-injection (DI). The reason I, and others have suggested this, is because, even if you move the common code into class libraries so that it can be shared by multiple projects, you'll still have the problem that your class libraries act as "black-boxes" that magically figure out everything for you and act appropriately in every situation. On the face of it, that sounds like a good thing for which a developer should strive, but in reality, that leads to bad code in the long run.
For instance, what happens when you want to use the same logging class library in 100 different projects and they all need to do logging in slightly different ways. Now, your class library has to magically detect all of those different situations and handle them all. For instance, some projects may need to save the log to a different file name. Some may need to store the log to the Windows event log or a database. Some may need to automatically email a notification when an error is logged. Etc. As you can imagine, as the projects increase and the requirements grow, the logging class library will need to get more and more complex and confusing which will inevitably lead to more bugs.
DI, on the other hand, solves all these issues, and if you adhere to the methodology, it will essentially force you to write reusable code. In simple terms, it just means that all the dependencies of a class should be injected (passed by parameter) into it. In other words, if the Logger class needs an event log, or a database connection, it should not create or reach out and find those things itself. Instead, it should simply require that those dependencies be passed into it (often in the constructor). Your example using DI might look something like this:
Public Interface ILogFilePathFinder
Function GetPath() As String
End Interface
Public Class LogFilePathFinder
Implements ILogFilePathFinder
Public Sub New(isOemVersion As Boolean)
_isOemVersion = isOemVersion
End Sub
Private _isOemVersion As Boolean
Function GetPath() As String Implements ILogFilePathFinder.GetPath
If _isOemVersion Then
Return "C:\TestOem.log"
Else
Return "C:\Test.log"
End If
End Function
End Class
Public Interface ILogger
Sub WriteLog(ByVal text As String)
End Interface
Public Class FileLogger
Implements ILogger
Public Sub New(pathFinder As ILogFilePathFinder)
_pathFinder = pathFinder
End Sub
_pathFinder As ILogFilePathFinder
Public Sub WriteLog(text As String) Implements ILogger.WriteLog
Dim path As String = _pathFinder.GetPath()
' Write text to file ...
End Sub
End Class
As you can see, it requires a little bit of extra work, but when you design your code like this, you'll never regret it. You'll notice that the logger class requests a path finder as a dependency. The path finder, in turn, requests an OEM setting as a dependency. So, to use it, you would need to do something like this:
Dim pathFinder As ILogFilePathFinder = New FileLogPathFinder(_OemSettings.IsOemVersion) ' Note, don't use a global to store the settings, always ask for them to be injected
Dim logger As ILogger = New FileLogger(pathFinder)
logger.WriteLog("test")
Now, you can easily reuse all of this code in any situation. For instance, if you have different projects that need to use a different log file path, they can still use the common FileLogger class, they just need to each implement their own version of ILogFilePathFinder and then inject that custom path finder into the common FileLogger. Hopefully you see how doing it this way can be very useful and flexible.