I inherited a VB.NET project that uses a module to check some user permissions.
One of the functions is called UserLevel and it makes a few calls to a hardware .dll file to read some serial numbers. The function looks like this:
Public Function UserLevel() As Integer
Dim status As Integer = userLevelWrapper.hardware.ReadInteger()
If status = 0 Then
If userLevelWrapper.readDaysLeft() > 0 Then
Return 2 'The user is a temporary user
Else
Return 0 'The user is invalid
End If
ElseIf Environment.UserName = "User1" OrElse _
Environment.UserName = "User2" Then
'There are more of these, and the user names are obviously not "User1" and "User2" because I've changed them for posting purposes.
Return 1 'The user is a developer
Else
Return 0 'The user is invalid
End If
End Function
For some reason I'm not able to step into this module when debugging. I can't figure out how it's configured, so I've had to make changes as I go and just check the result. I'm just trying to have the module tell my application that I'm a developer so I get uninterrupted use of the app. I've done this successfully by changing the UserLevel function to look like this:
Public Function UserLevel() As Integer
Return 1 'I am a developer
'All other code remains the same, and is not commented.
End Function
Because I don't have that userLevelWrapper package properly installed, I get a FileNotFoundException when actually executing the code. I assumed that happened when the Dim status As Integer... line is executed, so I altered that line to be
Dim status As Integer = 123456789 'userLevelWrapper.hardware.ReadInteger()
which will obviously force the If statement to evaluate to false. When I run it, I still get the FileNotFoundException.
I found out that the function is generating the exception because there exists a call to the hardware .dll on the 5th line where it's calling .readDaysLeft(), even though it's not being executed. When I comment that line and the .ReadInteger() call, no exception is thrown.
My question in this case is two parts:
1. Why doesn't the exception get thrown when the first line of the function is Return 1, and I haven't commented the hardware calls?
2. Why is the module trying to resolve where the file is when code is not being executed?
It makes sense to try to resolve the file at compile-time, but why is it just generating its own rules here by resolving it at run time but only under some arbitrary conditions?
It's not arbitrary.
When the code is commented out, it will not be compiled into the IL code that is translated by the JIT compiler into machine code.
When you don't comment it out, whether that branch of the code is compiled to IL or not becomes a compiler implementation detail that you can't make assumptions about. Maybe it is smart enough to know that branch will never get executed... maybe that analysis costs to much so it doesn't do it. The JIT compiler might compile your code to the machine level at a function level, in which case it will have to compile the entire function to machine code. At that time it'll need to find the assembly containing the function you call and fail.
It is possible that if you change status to a Constant the compiler might recognize that there's a branch that will never be executed and optimize it out of the IL.
Related
TL:DR; How can I compile a VB6 module file into a standard DLL which I can use across multiple VB6 applications?
I am tasked with the support of multiple legacy applications written in VB6.
All of these applications make use of piece of hardware constructed by my employer. Before I came on to work for my employer, he had outsourced the work of developing a DLL for the project to a company that is no longer capable of supporting it since the individual working for THEM recently quit and no one else is capable of figuring it out.
My employer has recently upgraded our hardware, so even worse - the DLL that Company furnished us with is no longer useful either.
Further exacerbated by the fact that the company who released to us the NEW hardware did not release to us a DLL file which is capable of running in VB6.
It now falls to me to create a DLL file ( NOT a device driver ) which is capable of facilitating communications between the new ( and hopefully the old ) devices and VB6 applications.
My knowledge of VB6 is... limited, at best. I am mostly familiar with .Net and have had much success in creating DLLs in .Net, but when it comes to VB6, I know enough to get by. I'm entering into uncharted territory here.
I'm well acquainted with the HID.dll and the SetupAPI.dll P/Invokes and structs necessary to make this work, and I was even fortunate enough to stumble upon this, which had a working bit of VB6 code which facilitates read/writing to/from HIDs connected to the system. I tested this and ( with a bit of fidgeting ) it worked for our device out of the box. But that doesn't help me because I can't compile the module into a DLL file ( let alone figuring out events in VB6 and a truck load of other things, but I'm getting ahead of myself ).
I've read and tried a few different methods and while they proved promising, they didn't work.
Google has also inundated me with a lot of red herrings and been in general not very helpful.
If necessary, I would even write it in C/C++ ( though I'd rather not if there is some other way ).
So is what I am trying to do possible? Can someone direct me to some step-by-step for this sort of thing?
EDIT 1 :
To expound a bit, when I say that "they didn't work", what I mean is that in the case of the first link, the program still failed to find the function ( with an error message like "Function entry point not found" ) and in the second case I consistently and repeatedly received a memory write error when trying to call the function ( not fun ).
Here's a link to a way to do a standard DLL, that looks more straightforward than the links you've posted. I can say that if Mike Strong ("strongm") posts code, it works, too. You might want to have a look at it.
However, it's probably better to use COM if you're able: it's easier to set up (obviously) and it also has some standard capabilities for keeping track of the object's interface, that are built into VB6. For example, when you use the TypeOf keyword, VB6 actually makes an internal call to the object's QueryInterface method, which is guaranteed to exist as one of the rules of COM (and, if you use the keyword on a reference to a standard DLL object you'll get an error).
VB6 does "static" classes by setting the class's Instancing property to GlobalMultiUse. Warning: the "static" keyword has an entirely different meaning in VB6: static local variables' values persist between method calls.
1. After your trip to 1998 to get your copy of VB6, start a new ActiveX DLL project:
2. Edit Project Properties for the name of the beast.
3. Add a Class for the interface you are creating. I cleverly named the class VB6Class because the project/DLL is named VB6DLL.
4. Write code. I added some test methods to perform complex calculations:
Option Explicit
Public Function GetAString(ByVal index As Integer) As String
Dim ret As String
Select Case index
Case 0
ret = "Alpha"
Case 1
ret = "Beta"
Case Else
ret = "Omega"
End Select
GetAString = ret
End Function
Public Function DoubleMyInt(ByVal value As Integer) As Integer
DoubleMyInt = (2 * value)
End Function
Public Function DoubleMyLong(ByVal value As Long) As Long
DoubleMyLong = (2 * value)
End Function
5. Make DLL from File menu. You may need to be running As Admin so it can register the DLL.
6. In the project which uses it, add a reference to the DLL.
Test code:
Private Sub Command1_Click()
Dim vb6 As New VB6DLL.VB6Class
Dim var0 As String
Dim var1 As Integer
Dim var2 As Long
var0 = vb6.GetAString(0)
var1 = vb6.DoubleMyInt(2)
var2 = vb6.DoubleMyLong(1234)
Debug.Print "GetAString == " & var0
Debug.Print "DoubleMyInt == " & var1
Debug.Print "DoubleMyLng == " & var2
End Sub
Result:
GetAString == Alpha
DoubleMyInt == 4
DoubleMyLng == 2468
Not sure what to do about the "truck load of other things".
I have a set of classes to which I am trying to apply unit tests, to maintain their current utility through future revisions.
My problem is that within CPPUNIT, to which I am new, where-ever I call CPPUNIT_ASSERT ( [condition] ), I am met with Error Unhandled Exception...: Access Violation at 0xffffffffffffffff.
This happens even I write the simplest test case
int main(){
CPPUNIT_ASSERT ( true );
}
I have tried calling my testing functions with manual calls, as well as adding them to a registry, as is done in the Money example. The problem reportedly arises within the constructor for SourceLine, as the filename string it expects is a bad pointer.
After a bit of a search I've found that this is called within the CPPUNIT_ASSERT, as it's a macro with the following definition
#define CPPUNIT_ASSERT(condition) \
( CPPUNIT_NS::Asserter::failIf( !(condition), \
CPPUNIT_NS::Message( "assertion failed", \
"Expression: " #condition), \
CPPUNIT_SOURCELINE() ) )
I've searched the tutorials on CppUnit's site, and scrutinised stackoverflow, but I have not found anything that addresses this in particular. I do find it strange that what is, in every example I've seen, a single-parameter function (assert), will call another function with no arguments (sourceline) that is actually another macro that is assuming it receives a string, but can receive no such thing. I found that SourceLine is a class that still has a default constructor, but above is called a macro, which really refers to the 2-parameter constructor, but is passed no arguments that I can see. I am at a loss.
I am using a 64 bit compilation of CppUnit, verified with a dumpbin, and Visual Studio 2008.
Cppunit's assertion system uses macros so it is expected that your simple example complains about unhandled exception.
Normally you don't use an assertion outside of a test method. I suggest you have a look at the Cppunit Cookbook which provides some information and examples how to effectively use cppunit.
Bear with me here, ok!!
We use SMO a lot to do all kinds of things, including to check for the presence of particular stored procedures in the database. So we have a method in our data access class called HasProc, which returns a boolean. It's in a part of the application that hasn't been changed for over a year, possibly two years.
Lately, it's been taking ages (10s) to return a value, and I've been trying to pin down why.
It turns out that even defining the variable that will hold the SMO Server (not instantiating it, just defining it) causes a 10s delay in the code arriving into the function.
Here's the relevant bit of the code, which just returns True now, for clarity:
Public Function HasProc(ByVal storedProcName As String) As Boolean
Dim s As Microsoft.SqlServer.Management.Smo.Server
Return True
End Function
In Visual Studio 12, stepping through the code using F11, the 10 second delay happens before the code highlight arrives at Public Function etc...
If I comment out the Dim statement, it all runs instantly.
Even more weirdly, if I disable my ethernet adaptor, the delay does not occur.
This is reproducible across three computers. I'm using VS2012, and SMO v11, to which we recently upgraded in order to support SQL Server 2012.
The other thing is that the delay happens even if the Return True statement is before, rather than after the Dim statement.
Any ideas?
This would happen if the static initializer for that class performs network IO (which is generally a bad idea).
If you pause the debugger during the delay, you can find out exactly what it's doing.
Today I discovered that something I had assumed about VB.NET for many years was not true (worrying!). I assumed that a variable declared within a loop had a lifetime of the iteration it was declared in, but in fact it seems it has a lifetime of the whole procedure.
For example:
For i As Integer = 0 To 1
Dim var1 As Boolean
Console.WriteLine(var1.ToString())
var1 = True
Console.WriteLine(var1.ToString())
Next
Console.ReadKey()
I had assumed an output of False, True, False, True but instead it is actually False, True, True, True.
In C# the equivalent code would not compile as you would get a compile time error of Error "Use of unassigned local variable 'var1'".
I realise there are many ways to fix this and that best practice would be to declare the variable outside of the loop and reset it at the beginning of every loop through.
I find this behaviour so counter-intuitive to me that I would like at least a compile time warning in VB.NET when/if I do this. (I could also then set this on any projects I already have and get warning that would allow me to check that my assumptions aren't causing errors).
Does anyone know how/if I can get this to generate a compile time warning in VB.NET? Am I the only one that finds this counter-intuitive?
We'll have to work on fixing your intuition because getting an error out of the compiler is not an option. It is partially implemented, you can get this warning:
error BC42104: Variable 'mumble' is used before it has been assigned a value. A null reference exception could result at runtime.
And elevate it from a warning to an error with Project + Properties, Compile tab. However, as the warning message indicates, this is only supported for reference type references, it won't budge for a variable of a value type.
Okay, intuition. If the runtime would implement your desired behavior then it would have to allocate a new variable for each iteration of the loop. Which implies that the number of local variables is bounded only by the number of iterations. This is very wasteful and a very easy trigger for StackOverflowException. The JIT compiler doesn't do this, it re-uses the variable. This happens in C# as well, minus the option of letting you not initialize the value explicitly of course.
Fwiw: I very much agree with you that this is unhelpful behavior. You'll probably find receptive ears at connect.microsoft.com, post your feature request there and the VB.NET team will see it. There has been strong backing from customers as well as within MSFT to make VB.NET and C# feature comparable. If you post a link to your feedback report then I'll be happy to vote it up.
I'm having trouble with a .NET Assembly that is com visible, and calling certain methods from VB6.
What I have found is that if the parameters are well defined types, (e.g. string), calls work fine. If they are higher level objects, it raises a runtime error '438' suggesting that the property or method is not present. I suspect that this is a question of having the correct signature on the call, but I can't see how to do this correctly.
I believe that I've done everything correct on the .NET side (ComVisible, public interfaces, etc. and even have it down to a simple enough case).
Looking at the output from the typelib viewer, I have the following:
dispinterface ISimple {
properties:
methods:
[id(0x60020000)]
void Add([in] ISimpleMember* member);
[id(0x60020001)]
ISimpleMember* Create();
};
OK. So I have 2 methods in my ISimple interface. One takes an ISimpleMember (Add), whilst the other, returns an ISimpleMember.
The corresponding code in VB looks like this:
Dim item As ISimpleMember
Dim simple As simple
Set item = New SimpleMember
item.S1 = "Hello"
item.S2 = "World"
Set simple = New simple
simple.Add (item) <---- This raised the run time error 438
Set item = simple.Create <---- This works fine, returning me an ISimpleMember
I've tried a couple of things:
1. Dim item as SimpleMember (makes no difference)
2. simple.Add(ObjPtr(item)) - Syntax error
3. simple.Add(ByRef item) - Syntax error
Basically, The run time error is the same as if I had
simple.AMethodThatIHaventWritten()
Also, If I browse References in the VB6 Environment, The Add method is well defined:
Sub Add(member As SimpleMember)
I've found the answer I believe. It was very simple:
When calling a SubRoutine, I shouldn't put the name in braces. the call should have been:
simple.add member
rather than
simple.add(member)
If I change it to a function (i.e. return a value rather than void) the braces are necessary
This seems to work
(Probably) The top 3 VB6 coding mistakes made by devs who now mainly code in C#, Javascript etc. Are:-
Placing ; at the end of lines. Its a syntax error very easily spotted and picked up the compiler.
Not placing Then on the other side of an If condition expression. Again its a syntax error.
Calling a method without retrieving a value and yet using ( ) to enclose the parameter list. With multiple parameters this is a syntax error and easily found. With only one parameter the use of ( ) is interpreted as an expression. Its the result of the ( ) expression which is passed as parameter. This causes problems when ByRef is expected by the callee.