I have followed the tutorial on the following page to create a c++ DLL and I have put it in the System32 folder: http://msdn.microsoft.com/en-us/library/ms235636%28v=vs.80%29.aspx. I am able to run the .exe from anywhere on the PC. Now I want to be able to call Add from a VB.NET application, so I have added the following code:
Imports System.Runtime.InteropServices
Public Class Form1
Private Sub Form1_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
Try
Dim Test As Integer
Test = Add(1, 1)
MsgBox(Test)
Catch ex As Exception
End Try
End Sub
<DllImport("MathFuncsDll.dll", EntryPoint:="Add", SetLastError:=True, CharSet:=CharSet.Ansi)> _
Private Shared Function Add(ByVal a As Double, ByVal B As Double) As Double
End Function
End Class
I get the following error: Unable to find an entry point named 'Add' in DLL 'MathFuncsDll.dll. I believe this is because of the namespace. I have researched this and some web pages say namespaces are not allowed with Platform Invoke and some web pages say they are allowed. What is the problem?
The entry point is not named "Add". From the Visual Studio Command Prompt, run dumpbin /exports MathFuncsDll.dll to see the exported names. To get this declaration:
<DllImport("MathFuncsDll.dll", EntryPoint:="?Add#MyMathFuncs#MathFuncs##SANNN#Z", _
CallingConvention:=CallingConvention.Cdecl)> _
Private Shared Function Add(ByVal a As Double, ByVal B As Double) As Double
End Function
The strange looking name is produced by the C++ compiler, a feature called "name decoration". It supports function overloading. You can put extern "C" in front of the function declaration to suppress it. It is actually better if you don't. Also note that SetLastError wasn't correct, the code doesn't actually call SetLastError() to report errors. And CharSet wasn't appropriate, these functions don't take strings.
You'll also need to do something about the Divide function, throwing a C++ exception won't come to a good end in an interop scenario, only C++ code can catch the exception.
Namespaces are not allowed. PInvoke works like plain C client. Don't forget also to declare MathFuncsDll as extern "C" to prevent C++ name mangling. Use Dependency Walker or dumpbin to see list of functions exported from MathFuncsDll.
Related
I am converting an old application coded with VB6 to VB.NET.
In my old application I was using a third party DLL file. Let's call it ThirdParty.dll.
One of my usages of that ThirdParty.DLL was like that in VB6:
Dim MyApi as new x.y 'referenced from the ThirdParty.dll
Dim vReturnCode as Long
vReturnCode = MyAPI.InitialiserCles(a, b, c, d, e) 'consider a,b,c,d,e variables declared and initiaited.
In the old application, this would return a result as Long.
Now, I need to use that same ThirdParty.dll in my new VB.NET application.
What I created was this:
Public Class ThirdPartyAPI
Private Declare Function InitialiserCles Lib "ThirdPartyFolder\ThirdParty" (a As String, b As String, c As String, d As String, ByRef e As String) As Long
Public Function InitializeKey(a As String, b As String, c As String, d As String, ByRef e As String) As Long
Return InitialiserCles(a, b, c, d, e)
End Function
End Class
Now when I want to use that, I am doing it like this:
Dim MyApi as new ThirdPartyAPI
Dim result as MyApi.InitialiserCles(a,b,c,d,e) 'consider again variables are well declared/initiated.
I am getting the following error:
Can't find DLL entry point 'InitialiserCles' in DLL ThirdPartyFolder\ThirdParty
I guess this is happening because we were actually creating a new instance of x.y from that DLL, then calling the function InitialiserCles (in the old applciation (VB6)). I failed to do the same in VB.NET. What am I missing?
Since this my first time working with COM DLLs and other stuff, it wasn't easy for me. Thanks for the comments above I was able to do it.
What ended up working for me was the next:
Since ThirdParty.DLL was a COM DLL I had to register it using regsvr32 command. Note that I had to use the one located inside SysWOW64 for it to work. Obviously because it's a 32 bit DLL file. The command then is:
regsvr32 "FullPath\ThirdParty"
After running this command successfully, from visual studio I was able to add a reference from com objects called ThirdParty and then I was able to create instance of classes from that DLL.
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.
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
I want to embed a simple piece of VBA Code in Access 2007. I need to execute this code on hundreds of different Access DBs, so I don't want to manually paste the code into each and every DB. Is it possible to do this? Maybe by way of an add-in?
Thanks
Karl
EDIT
I want to execute the following VBA code:
DoCmd.DeleteObject acTable, "LastNum"
DoCmd.TransferDatabase acLink, "ODBC Database", "ODBC;DSN=myDB;UID=User1;PWD=123;LANGUAGE=u s_english;" & "DATABASE=LastNumber", acTable, "LastNum", "LastNum"
How would I translate this into a VB addin?
The visual studio VB add-in template looks like this:
imports Extensibility
imports System.Runtime.InteropServices
<GuidAttribute("B61E2444-F46E-4591-A8BA-3D06A4E5D84C"), ProgIdAttribute("MyAddin1.Connect")> _
Public Class Connect
Implements Extensibility.IDTExtensibility2
Private applicationObject As Object
Private addInInstance As Object
Public Sub OnBeginShutdown(ByRef custom As System.Array) Implements Extensibility.IDTExtensibility2.OnBeginShutdown
End Sub
Public Sub OnAddInsUpdate(ByRef custom As System.Array) Implements Extensibility.IDTExtensibility2.OnAddInsUpdate
End Sub
Public Sub OnStartupComplete(ByRef custom As System.Array) Implements Extensibility.IDTExtensibility2.OnStartupComplete
End Sub
Public Sub OnDisconnection(ByVal RemoveMode As Extensibility.ext_DisconnectMode, ByRef custom As System.Array) Implements Extensibility.IDTExtensibility2.OnDisconnection
End Sub
Public Sub OnConnection(ByVal application As Object, ByVal connectMode As Extensibility.ext_ConnectMode, ByVal addInInst As Object, ByRef custom As System.Array) Implements Extensibility.IDTExtensibility2.OnConnection
applicationObject = application
addInInstance = addInInst
End Sub
End Class
EDIT PART 2:
Ok, so I figured out I should do the following:
imports Extensibility
Imports System.Runtime.InteropServices
Imports Microsoft.Office.Core
Imports Access = Microsoft.Office.Interop.Access
<GuidAttribute("B61E2444-F46E-4591-A8BA-3D06A4E5D84C"), ProgIdAttribute("MyAddin1.Connect")> _
Public Class Connect
Implements Extensibility.IDTExtensibility2
Private applicationObject As Access.Application
Private addInInstance As Microsoft.Office.Core.COMAddIn
Public Sub OnBeginShutdown(ByRef custom As System.Array) Implements Extensibility.IDTExtensibility2.OnBeginShutdown
End Sub
Public Sub OnAddInsUpdate(ByRef custom As System.Array) Implements Extensibility.IDTExtensibility2.OnAddInsUpdate
End Sub
Public Sub OnStartupComplete(ByRef custom As System.Array) Implements Extensibility.IDTExtensibility2.OnStartupComplete
End Sub
Public Sub OnDisconnection(ByVal RemoveMode As Extensibility.ext_DisconnectMode, ByRef custom As System.Array) Implements Extensibility.IDTExtensibility2.OnDisconnection
End Sub
Public Sub OnConnection(ByVal application As Object, ByVal connectMode As Extensibility.ext_ConnectMode, ByVal addInInst As Object, ByRef custom As System.Array) Implements Extensibility.IDTExtensibility2.OnConnection
applicationObject = CType(application, Access.Application)
addInInstance = CType(addInInst, Microsoft.Office.Core.COMAddIn)
' This line enables VBA to call back into this object.
addInInstance.Object = Me
End Sub
Public Sub ChangeLink()
applicationObject.DoCmd.DeleteObject(Access.AcObjectType.acTable, "LastPolNum")
applicationObject.DoCmd.TransferDatabase(Access.AcDataTransferType.acLink, "ODBC Database", "ODBC;DSN=ZACANTDB02;UID=EDIPolicyNumber;PWD=museum123;LANGUAGE=u s_english;" & "DATABASE=EDIPolicyNumber", Access.AcObjectType.acTable, "LastPolnum", "LastPolNum")
End Sub
End Class
Now what I want is to be able to execute ChangeLink() from Access. How do I do that?
Depending on what the code is for, there are (at least) two different ways to do this, without working at the VB Extensions level (which is where you'd copy and paste code, at least in VBA):
Make the code part of an add-in. It can then be loaded, and stay loaded and exposed, to any other application.
If the code is working on data only, consider writing a procedure that links and unlinks the table(s) in each mdb file that needs processing.
The advantage of #2 over #1 (and, indeed, over copying and pasting code) is that you can invoke your simple routine once, not hundreds of times.
EDIT:
There are two ways to invoke an add-in, depending on who is meant to invoke its functionality, and how.
Through the UI. Use this when you want an end user to be able to use it, especially practical for functionality you'll invoke often. How to:
a. Office button > Access options > Add-ins tab
b. If it's a .NET add-in (or any other non-Access add-in), choose COM Add-in from Manage dropdown. If it's Access, choose Access. Click go
c. Click Add button in the form that pops up, browse to the add-in.
You will still need to provide some sort of UI to it: a commandbar, a ribbon button, a form, something. That's a separate discussion
Through code. Use this if you're just wanting to invoke it as part of some other VBA procedure.
a. VBE window > Tools > References
b. Browse button
c. Change the dropdown in file of type if you're setting up an Access add-in.
You can then call the line of code directly, as if it were part of your current project. If you wish to be more explicit, you can also invoke it by library name: MyAddInName.ChangeLink
Seems to me that you don't need to run this code from the Access databases -- all you need to do is run it on all the databases.
To do that, you'd use DAO to open each database in turn, delete the table, then create the link. You'll have to do both of those steps with DAO (deleting from the TableDefs collection, and adding to it) rather than with DoCmd operations (which are unavailable through DAO).
Of course, if the databases are no accessible from a central location, you can't do this. But if that's the case, how would you alter the code or invoke an add-in?
Another way to use a library database is to use Application.Run:
Application.Run "\Server\PathToAddIn\MyLibrary.FixTables"
This assumes:
MyLibrary is an MDE/ACCDE.
in MyLibrary database is a subroutine or function called FixTables that runs the code you want to execute.
Indeed, with that method, you can actually use DoCmd, since the add-in is running within Access and in the context of the currently opened database.
However, note that some people seem to have difficulties executing add-ins in this fashion (I'm currently in a long discussion with somebody in another forum who can't seem to get it to work). I've been using this method for library databases for years and years and have had no problems (as long as you specify a full path), so I'm puzzled why the ones I'm discussing it with can't seem to make it work.
I'm having some trouble finding the syntax for making function calls to unmanaged DLLs in VB.NET. Is anyone familiar with this?
Let's just assume there's a function "Connected" in unmanaged DLL "Connector.DLL". I want to call this function by creating an abstract function call to it.
I've seen some code out there that looks something like
[DllImport("Connector.DLL")]
Public Shared Function Connect(ByVal intPort)
But that syntax doesn't work for me.
Try the following code.
Public Declare Function Connect Lib "Connector.DLL" (<MarshalAs(UnmanagedType.I4)> ByVal intPort As Integer) As Integer
In Visual Studio, add a reference to this Dll.
In Code:
Dim vr as new COMDllClass()
vr.FunctionInDll()
EDIT per comment:
Try this code:
<DllImport("Connector.DLL")> _
Public Shared Function Connect(ByVal intPort)