I'm converting some code from VB6 to VB.Net and it contains a number of occurrences of:-
Private m_myobj As ObjectContext
m_myobj = GetObjectContext()
' do stuff, then
m_myobj.SetAbort()
' or
m_myobj.SetComplete()
By dint of including a reference to System.Data and System.Data.Entity and Imports System.Data.Object I have managed to get the declaration to compile, but the others have so far resisted. The errors showing are:-
'GetObjectContext' is not declared. It may be inaccessible due to its protection level
'SetComplete' is not a member of 'System.Data.Objects.ObjectContext'
'SetAbort' is not a member of 'System.Data.Objects.ObjectContext'
It would appear from the documentation that the two methods don't actually exist but they (presumably) must have worked in the VB6. Does anyone know what I should do about this?
The SetAbort and SetComplete methods are calls out to the COM+ (Was once called MTS) application that the class is running in, and allow parts of the code to vote on whether distributed database transactions will be committed by the com+ environment. You will want to look at the code path and see whether the code is required or not. If it is you will want to investigate other methods of extending database transactions across multiple DB accesses. In my experience people sometimes got excited about this technology and implemented it unnecessarily and it is quite possible that you can just eliminate the code.
The simplest way to duplicate this functionality would be to maintain an open connection and call begintran and endtran appropriately, this kind of stuff tend to get complicated though.
I suppose the closest modern Microsoft equivilant is Entity Framework.
GetObjectContext is a Windows function. You can declare it using P/Invoke like this:
<DllImport("ComSvcs.dll", CallingConvention:=CallingConvention.Cdecl)> _
Public Shared Function GetObjectContext(<Out> ByRef pCtx As IObjectContext) As Integer
End Function
<ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("51372AE0-CAE7-11CF-BE81-00AA00A2FA25")> _
Public Interface IObjectContext
Function CreateInstance(ByVal rclsid As Guid, ByVal riid As Guid) As Object
Sub SetComplete()
Sub SetAbort()
Sub EnableCommit()
Sub DisableCommit()
<PreserveSig> _
Function IsInTransaction() As Boolean
<PreserveSig> _
Function IsSecurityEnabled() As Boolean
Function IsCallerInRole(<MarshalAs(UnmanagedType.BStr)> ByVal role As String) As Boolean
End Interface
Related
I'm using system image list in my application, as will be explained below. Calling this from the main thread works perfectly, however, if when I try to do this from a different thread, it causes error for a reason that I don't really understand.
The system image list interface is:
<ComImportAttribute(), GuidAttribute("46EB5926-582E-4017-9FDF-E8998DAA0950"), InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)> _
Private Interface iImageList
<PreserveSig> _
Function Add(ByVal hbmImage As IntPtr, ByVal hbmMask As IntPtr, ByRef pi As Integer) As Integer
'here comes the rest of standard functions for this interface
End Interface
The Windows API definition is:
<DllImport("shell32.dll", EntryPoint:="#727")> _
Private Shared Function SHGetImageList(ByVal iImageList As Integer, ByRef riid As Guid, ByRef ppv As IImageList) As Integer
End Function
And finally, to get\create the image list, I am executing the following:
Dim ID As New Guid("46EB5926-582E-4017-9FDF-E8998DAA0950")
Dim Intrf As iImageList=Nothing
Dim extraLargeIcons = &H2
Dim Ret As Integer = SHGetImageList(CInt(Fix(extraLargeIcons)), ID, Intrf )
This last call to create the list casing the following error if called from a non-main thread (simply calling it from standard .Net Background worker):
Unable to cast COM object of type 'System.__ComObject' to interface type 'iImageList'. This operation failed because the QueryInterface call on the COM component for the interface with IID '{46EB5926-582E-4017-9FDF-E8998DAA0950}' failed due to the following error: No such interface supported (Exception from HRESULT: 0x80004002 (E_NOINTERFACE)).
I would like to understand why this happening and why iImageList interface become suddenly not supported when created in a different thread (it works perfectly from main thread), and if there is a way to resolve this.
P.S.
I did read similar questions, but I am not passing information across threads (I am trying to create and use Imagelist from one thread), also I did not understood how to resolve this. Using STA\MTA-Thread attribute gave nothing. Finally, I should say that I know little about COM business.
A call to PInvoke function 'ReleaseCapture' has unbalanced the stack.
This is likely because the managed PInvoke signature does not match
the unmanaged target signature. Check that the calling convention and
parameters of the PInvoke signature match the target unmanaged signature.
The function has been defined this way and has been working for over 6 years just fine. We didn't get word of this error until a user reported it. It happens when a user starts to drag a user control on the screen, if it's not dragged it is fine.
<DllImport("user32")> _
Public Shared Function ReleaseCapture(ByVal hwnd As IntPtr) As Integer
End Function
This function is called on the user control MouseDown event. For example:
Private Sub uxCalcTitleBar_MouseDown(ByVal sender As System.Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles uxCalcTitleBar.MouseDown, lblCalcTitle.MouseDown
If e.Button = Windows.Forms.MouseButtons.Left And e.Clicks = 1 Then
If Not Me._CalcIsMoving And Not Me._CalcIsPackedForMove Then
Me.Calc_PackForMove()
End If
ReleaseCapture(Me.uxCalculator.Handle) **ERROR HERE**
SendMessage(Me.uxCalculator.Handle, WM_SYSCOMMAND, MOUSE_MOVE, 0)
Me._CalcNewLocation = Me.uxCalculator.Location
Me.uxCalcTitleBar_MouseUp(sender, e)
End If
End Sub
One thing we noticed, this started happening after moving to the 4.5 framework from 2.0. Do not know if this makes a difference, but I think it should not. After some research I found that the resolution should be reviewing the managed platform invoke signature and calling convention to confirm it matches the signature and calling convention of the native target.
What I have Tried
I examined the signature and it seem's to be just fine, nothing I can actually see. I also specified the convention as such to clear the stack it doesn't help...
<DllImport("user32", CallingConvention:=CallingConvention.Cdecl)> _
Public Shared Function ReleaseCapture(ByVal hwnd As IntPtr) As Integer
End Function
The correct signature is this:
<DllImport("user32.dll")> _
Public Shared Function ReleaseCapture() As Boolean
End Function
The function does not take any parameters as can be seen from the documentation: https://msdn.microsoft.com/en-us/library/windows/desktop/ms646261.aspx
Regarding your use of CallingConvention.Cdecl, that is a mistake. The calling convention is CallingConvention.StdCall, which is the default and so can be omitted. You don't get to decide what the calling convention is any more than you get to decide what the parameters are. You cannot decide to impose CallingConvention.Cdecl as a means to "clear the stack". That is just meaningless. The implementer of the function decides its calling convention, parameters and so on. Your job is to meet the interface contract specified by the implementer of the function.
One thing we noticed, this started happening after moving to the 4.5 framework from 2.0.
Indeed. Version 2.0 of .net did not contain the pInvokeStackImbalance MDA that is producing this message. Your program has been wrong for all that time and you've just been lucky. Now that you are using better tooling, that tooling has been able to inform you of your error.
We have this:
Friend NotInheritable Class ConcreteGraphFactory
Inherits AbstractGraphFactory
Public Shared ReadOnly Instance As New ConcreteGraphFactory()
Private Sub New()
MyBase.New()
End Sub
Friend Overrides Function Create() As AbstractGraph
Return New ConcreteGraph()
End Function
Private NotInheritable Class ConcreteGraph
Inherits AbstractGraph
Private ReadOnly Question1 As New Question("Why isn't this showing a warning?")
Public Overrides Function GetRoot() As IRoot
Return Question1 '<---HERE
End Function
Public Sub New()
End Sub
End Class
End Class
And I have IRoot:
Friend Interface IRoot
Inherits IQuestion
Function GetContainer() As AbstractGraph
End Interface
And finally Question:
Public Class Question
Implements IQuestion
' code....
End Class
Why would VS not show a warning? Question does not implement IRoot...
If you want the compiler to give an error there, then you need to set Option strict to On. You can do that on the Compile tab of the project's Properties. Or add Option Strict On to the top of the file that contains this code.
Here are a few pages that have more details about what Option Strict means.
http://support.microsoft.com/en-us/kb/311329
https://msdn.microsoft.com/en-us/library/zcd4xwzs.aspx
Option Strict Off means that the Visual Basic compiler doesn't enforce strict data typing. It will try to do implicit type conversions and throw run time errors if that can't be done.
I didn't think it had anything to do with IRoot being an interface, but after trying it out it looks like it does. If GetRoot returned a class that Question didn't inherit from, then you would get a compiler error even with Option Strict off.
Running with Option Strict off actually makes some things easier, especially when dealing with late bound COM objects. For the most part, you don't have to worry about type casts when writing code.
However, it's also one of the reasons many people don't like VB.NET. Personally, I liked it when I was working with it, but it's been long enough now that it does seem strange that the compiler wouldn't be doing all the strict type checking for you. I could always tell when some VB code had been generated via a conversion tool from C# because it would have a bunch of DirectCast calls that you wouldn't see in code that a VB developer had written.
When C# came out with the dynamic keyword in 2009, the VB.NET developers were thinking, "Meh. We've always been able to write code without worrying about types." Of course, VB.NET wasn't the same as dynamic in C#, but many of the early dynamic code examples were showing things that you could already do in VB with option strict turned off.
I am trying to create a .Net DLL basically as an abstraction layer for database connections; it is going to replace a current DLL we have that is written in VB6 and I am trying to match the current functionality as much as possible.
Anyway, the essential issue I am having is that I can't find a way to get .Net classes like DataColumnCollection or DataColumn to display in the VBA Interpreter -- It may say, for example, "Column" with the type "MarshalByValueComponent," but the value will be "No Variables".
I can get it to work if I completely re-create both classes (i.e. Fields as a collection of field, which inherits from DataColumn, and then define an interface for both), but that seems like a lot of added overhead for what (should be?) a pretty simple idea. I feel like I am just missing something very simple with the way the marshaller is handling the DataColumn class.
A lot of the stuff I am finding online is on how to convert a DataTable or DataReader to a legacy ADODB Recordset, but that also would add a lot of overhead... I'd rather leave it as a DataTable and create a COM interface to allow VBA to interact with it; that way if, for example, they want to write the table to an excel sheet, I wouldn't be duplicating work (convert to ADODB recordset, then read/write to excel sheet. You'd need to iterate the entire table twice...)
Sorry for the book-length explanation -- I felt the problem needed a bit of clarification since the root-cause is trying to match legacy functionality. Here is an example of my current interface that does not work:
Public Interface IDataTable
ReadOnly Property Column As DataColumn
End Interface
<ClassInterface(ClassInterfaceType.None)> _
<System.ComponentModel.DesignerCategory("")> _
<ComDefaultInterface(GetType(Recordset.IDataTable))> _
<Guid("E7AFBBB6-CB20-44EC-9CD2-BC70B94CD8B7")> _
Public Class Recordset : Inherits Data.DataTable : Implements IDataTable
Public ReadOnly Property Column As DataColumn Implements IDataTable.Column
Get
Return MyBase.Columns(0)
End Get
End Property
Note: I originally tried the property Columns as DataColumnCollection which returned MyBase.Columns. That came through as an Object, instead of MarshalByValueComponent, but was also empty. I know MyBase.Column(0) has a value, because I can put Msgbox(MyBase.Columns(0).ColumnName) right above the return in the get and it pops up fine (don't judge; this is way easier than using a debugger for this)...
I wouldn't mind just defining them both, but I can't inherit DataColumnCollection and the COM interface already sucks at dealing with generics. Is there any other way around this without re-inventing the wheel?
Thanks for your help!
I just spent the last 3 weeks doing something eerily similar.
I ended up making two .NET assemblies:
A pure .NET assembly that talks to the datastore (for use by .NET apps).
A "COM Interop" assembly that wraps the first assembly and adds the COM overhead (ADODB references and COM-Visible interfaces).
I call the second assembly from Excel VBA using the VSTO "AddIn.Object" property.
I ended up converting System.Data.DataTables to ADODB.Recordsets as you mentioned. Getting .NET and VBA talking about anything other than primitive types was beyond-frustrating for me. In fact, I ended up serializing some objects as JSON so the two worlds could communicate.
It does seem insane, but I reinvented the wheel.
I followed this MSDN article to make my .NET code callable by VBA.
I used this Code Project article (I'm sure you've seen) to convert to Recordset*.
I let the frameworks handle string, integers, etc.
For all other data types I used Json.Net and a custom VBA class to do JSON serialization.
*Converted article to VB.Net and added some extra error handling.
Okay, this probably isn't the most elegant (or complete, at this point) solution; but I think it's the route I am going to go.
Instead of converting the whole thing to an ADODB Recordset (and duplicating any iterations), I just threw out the DataTable class entirely and wrote my own Recordset class as a COM Wrapper for the a generic Data Reader (via the IDataReader interface) and added a new Field class to manage the type conversion and set up Fields as an array of Field (since interop hates generics)
It basically creates a forward-only ADODB Recordset (same limitations) but has the benefit of only loading one row at a time, so the bulk of the data can be handled as managed code until you know what they want to do with it (I'm going to add methods for ToArray, ToAccessDB, ToFile, etc that use the reader) while still allowing the ability to iterate through the Recordset from excel/access/vbscript/vb6 (if that's really what they want to do.. mostly needed that for legacy support anyway)
Here is an example, in case anyone else has to do this again; somewhat modified for brevity:
Public Interface IRecordset
ReadOnly Property CursorPosition As Integer
ReadOnly Property FieldCount As Integer
ReadOnly Property Fields As Field()
Function ReadNext() As Boolean
Sub Close()
End Interface
<System.ComponentModel.DesignerCategory("")> _
<ClassInterface(ClassInterfaceType.None)> _
<ComDefaultInterface(GetType(IRecordset))> _
<Guid("E7AFBBB6-CB20-44EC-9CD2-BC70B94CD8B7")> _
Public Class Recordset : Implements IRecordset : Implements IDisposable
Private _Reader = Nothing
Private _FieldCount As Integer = Nothing
Private _Fields() As Field
Public ReadOnly Property CursorPosition As Integer Implements IRecordset.CursorPosition...
Public ReadOnly Property FieldCount As Integer Implements IRecordset.FieldCount...
Public ReadOnly Property Fields As Field() Implements IRecordset.Fields...
Friend Sub Load(ByVal reader As IDataReader)
_Reader = reader
_FieldCount = _Reader.FieldCount
_Fields = Array.CreateInstance(GetType(DataColumn), _FieldCount)
For i = 0 To _FieldCount - 1
_Fields(i) = New Field(i, Me)
Next
End Sub
'This logic kinda sucks and is dumb.
Public Function ReadNext() As Boolean Implements IRecordset.ReadNext
_EOF = Not _Reader.Read()
If _EOF Then Return False
_CursorPosition += 1
For i = 0 To _FieldCount - 1
_Fields(i)._Value = _Reader.GetValue(i).ToString
Next
Return True
End Function
From here you just need to define some type like Field or Column and add an interop wrapper for that type:
Public Interface IField
ReadOnly Property Name As String
ReadOnly Property Type As String
ReadOnly Property Value As Object
End Interface
<System.ComponentModel.DesignerCategory("")> _
<ClassInterface(ClassInterfaceType.None)> _
<Guid("6230C670-ED0A-48D2-9429-84820DC2BE6C")> _
<ComDefaultInterface(GetType(IField))> _
Public Class Field : Implements IField
Private Reader As IDataReader = Nothing
Private Index As Integer = Nothing
Public ReadOnly Property Name As String Implements IField.Name
Get
Return Reader.GetName(Index)
End Get
End Property
Public ReadOnly Property Value As Object Implements IField.Value
Get
Return Reader.GetValue(Index)
End Get
End Property
Public ReadOnly Property Type As String Implements IField.Type
Get
Return Reader.GetDataTypeName(Index).ToString
End Get
End Property
Sub New(ByVal i As Integer, ByRef r As IDataReader)
Reader = r
Index = i
End Sub
End Class
All of this is rather silly, but it seems to work well.
Note: I've only been using .Net for about 4 days now, so this might be terrible, please feel free to comment on anything extremely stupid I might be doing.
I just installed Visual Studio 2010 Service pack (proposed on Windows Update), and I can see a new feature on the "intellisense" that means when I write a Function or Sub in VB.NET it doesn't auto-complete parameters with ByRef or ByVal...
1) Is there anyway that I can configure this option back to how it was before?
2) If I don't specify ByX, which one is used by default? (it seems like it is always ByRef)
It seems that this post covers your question:
http://msmvps.com/blogs/carlosq/archive/2011/03/15/vs-2010-sp1-changing-quot-byval-quot-vb-net-code-editor-experience.aspx
So no, there is no way to get the old behaviour. From now on ByVal is the default (what it was before) and it won't get added automatically to the method parameters.
In my opinion this is a good decision since it's making VB.NET a bit more consistent with C# and avoids unnecessary "noises"(it's already verbose enough).
Old behaviour:
Private Sub test(ByVal test As String)
End Sub
New behaviour
Private Sub test(test As String)
End Sub
Tim covered what you asked directly, but something else to keep in mind is that any reference type variable, like a user defined class even if passed by value will allow you to make changes to that instances properties etc that stay. It won't however allow you to change the entire object. Which may be why it seemed to you to be defaulting to by reference
Public Sub (Something As WhateverClass)
Something = New WhateverClass 'will result in no changes when outside this method
Something.Property1 = "Test" 'will result in an updated property when outside this method
End Sub
From MSDN:
The value of a reference type is a pointer to the data elsewhere in memory.
This means that when you pass a reference type by value,
the procedure code has a pointer to the underlying element's data,
even though it cannot access the underlying element itself. For
example, if the element is an array variable, the procedure code does
not have access to the variable itself, but it can access the array
members.
Beware when transferring routines to VBA, where the default is ByRef (see, e.g., "The Default Method Of Passing Parameters" at the bottom of this page, by the great Chip Pearson).
That can be messy.