I am new to VB and is working on VB6 to VB.net migration. There is one API call which appends a prefix in temporary file name.
I have added the dll as
<DllImport("kernel32")> _
Private Shared Function GetTempFileName(ByVal lpszPath As String, ByVal lpPrefixString As String, ByVal wUnique As Long, ByVal lpTempFileName As String) As Long
End Function
When I am calling this:
test = GetTempFileName(My.Application.Info.DirectoryPath, Prefix, 0, m_sTempfile)
It throws an exception:
An unhandled exception of type 'System.AccessViolationException' occurred in Forum.exe
Additional information: Attempted to read or write protected memory. This is often an indication that other memory is corrupt.
I tried using Path.GetTempFileName() but I might need to perform several manipulation to get the file name prefixed with specific word and located to specific location.
I crossed checked the values and they are NOT bad data.
I tried mutiple resolutions, but none of it worked.
Can someone help in this? Thanks in advance!
Pinvoke declarations need to be rewritten when you move them to VB.NET. Many differences, like Long needs to be Integer and if the winapi function returns a string then you need to use StringBuilder instead of String. Required because String is an immutable type.
Proper declaration is:
<DllImport("kernel32", SetLastError:=True, CharSet:=CharSet.Auto)> _
Public Shared Function GetTempFileName(ByVal lpszPath As String, _
ByVal lpPrefixString As String, _
ByVal wUnique As Integer, _
ByVal lpTempFileName As StringBuilder) As Integer
End Function
And a proper call looks like:
Dim buffer As New StringBuilder(260)
If GetTempFileName("c:\temp", "xyz", 0, buffer) = 0 Then
Throw New System.ComponentModel.Win32Exception()
End If
Dim filename = buffer.ToString()
The pinvoke.net website tends to be a half-decent resource for pinvoke declarations. Not for this one though, the VB.NET version is pretty fumbled.
Related
I am trying to get an FTP Download operation to work with VBA Excel (on Excel 2013 x64, Windows 7 SP1 x64). I found some code that maps to the WinInet API and I can successfully upload files using the PutFile function but I am looking to get a GetFile function to work as well.
To get working what I have so far, I used the following: second answer here, and this link. I've been mostly using the second answer from SO to get PutFile to work. I had to make some changes to the original code to make it compatible with 32 bit and 64 bit systems.
You can see my progress here.
What I am looking to do is make an easy to use Sub that calls this declaration:
Private Declare Function FtpGetFile Lib "WinInet" Alias "FtpGetFileA" (ByVal hFtp As Long, ByVal lpszRemoteFile As String, ByVal lpszNewFile As String, ByVal fFailIfExists As Long, ByVal dwFlagsAndAttributes As Long, ByVal dwFlags As Long, ByVal dwContext As Long) As Long
I.E. a Sub like this:
Public Sub GetFile(RemoteFilename As String, LocalFilename As String)
If FtpGetFile(' what arguments do I put here ') = 0 Then
Err.Raise vbObjectError + 1, , LastError
End If
End Sub
I am struggling because I am not very familiar with the WinInet API and am having difficulty parsing the required arguments and what are the appropriate variables to pass for those arguments.
This should get you started on the right path, based on the example provided.
In your class module:
Public Sub GetFile(RemoteFilename As String, NewFilename As String)
If FtpGetFile(m_hFtp, RemoteFilename, NewFilename, False, 0, FTP_TRANSFER_TYPE_BINARY, 0) = 0 Then
Err.Raise vbObjectError + 1, , LastError
End If
End Sub
How to call it:
Sub DownloadFile()
Dim ftp As New CFtp
ftp.Connect "serverAddress", "username", "password"
ftp.GetFile "nameOfFileOnServer.txt", "C:\SomePath\nameOfNewFile.txt"
ftp.Disconnect
End Sub
The parameters specify some attributes to associate with the downloaded file. For example, fFailIfExists is a Boolean that describes whether or not to throw an exception if it is trying to overwrite a local file that already exists. The other flags specify attributes to attach to the file it creates.
Trying out some API programming, I encountered a problem with the capGetDriverDescription function of AVICAP32.dll:
Declare Function capGetDriverDescriptionA Lib "avicap32.dll" (ByVal wDriver As Short, _
ByVal lpszName As String, ByVal cbName As Integer, ByVal lpszVer As String, _
ByVal cbVer As Integer) As Boolean
As far as I know, the lpszName parameter is a buffer for getting the driver description, and I read somewhere that the buffer should be passed as a reference type (ByRef instead of ByVal), and of course it should be a reference because of storing the information I need after returning from the function. But in this function, it's a value type, and it works fine!
even I tried to change it to ByRef but the application didnt run!
What knowledge am I missing? How can this buffer store my information while it's being passed a value type?
This is how the code calls the function to get available webcams:
Private Sub LoadDeviceList()
On Error Resume Next
Dim strName As String = Space(100)
Dim strVer As String = Space(100)
Dim bReturn As Boolean
Dim x As Integer = 0
Do
bReturn = capGetDriverDescriptionA(x, strName, 100, strVer, 100)
If bReturn Then
lst1.Items.Add(strName.Trim)
End If
x += 1
Application.DoEvents()
Loop Until bReturn = False
End Sub
I'll start by saying I don't know much about this subject (marshaling data can be a deep subject), but I think Default Marshaling for Strings (MSDN) may help you. Scroll down to the part that says Fixed-Length String Buffers. According to this, when a string is marshaled to the API, even if passed byRef, it can't be modified by the callee.
I haven't tested, but according to the MSDN example, to get a value back from your function, the definition would become
Declare Function capGetDriverDescriptionA Lib "avicap32.dll" (ByVal wDriver As Short, _
ByVal lpszName As StringBuilder, ByVal cbName As Integer, ByVal lpszVer As String, _
ByVal cbVer As Integer) As Boolean
Changing lpszName from String to StringBuilder. Apparently StringBuilder will work as a buffer to get the string back from the function.
Then you would call your function like so,
StringBuilder sb = new StringBuilder(256)
capGetDriverDescription(Driver, sb, sb.Capacity + 1, ....
return sb.ToString()
I found a very similar question, How do I import and call unmanaged C dll with ANSI C string "char *" pointer string from VB.NET?.
I'm going little crazy about this and I already spent few hours to find the problem so I guess I need some WinAPI guru here :) I use VB .NET so please be kind :)
Trying to communicate with some USB device and at SetupDiGetDeviceInterfaceDetail() step, the (first) call to function will fail with ERROR_INVALID_USER_BUFFER. The second call will have non sense now :) The problem is why ? The DeviceInfoTable handle is correct since I use'it in few more functions with success and I tested Err.LastDllError after each call.
The InterfaceDataStructure I presume is also correct since otherways SetupDiEnumDeviceInterfaces() will fails. I suspect incorrect declarations of dll imports or structure.
If Not SetupDiGetDeviceInterfaceDetail(DeviceInfoTable, InterfaceDataStructure, Nothing, 0, StructureSize, Nothing) Then
ErrorStatus = Err.LastDllError ' <-- always 0x6F8, ERROR_INVALID_USER_BUFFER
End If
Here are declarations
Dim DeviceInfoTable As IntPtr = INVALID_HANDLE_VALUE
Dim InterfaceDataStructure As SP_DEVICE_INTERFACE_DATA = New SP_DEVICE_INTERFACE_DATA
Dim InterfaceIndex As Integer = 0
Dim ErrorStatus As Integer = 0
Dim DevInfoData As SP_DEVINFO_DATA = New SP_DEVINFO_DATA
Dim dwRegType As Integer
Dim dwRegSize As Integer
Dim DetailedInterfaceDataStructure As SP_DEVICE_INTERFACE_DETAIL_DATA = New SP_DEVICE_INTERFACE_DETAIL_DATA
Dim StructureSize As Integer = 0
'Structures declarations
<StructLayout(LayoutKind.Sequential, Pack:=1)> _
Public Structure SP_DEVINFO_DATA
Public cbSize As UInteger
Public InterfaceClassGUID As Guid
Public DevInst As UInteger
Public Reserved As IntPtr
End Structure
<StructLayout(LayoutKind.Sequential, Pack:=1)> _
Public Structure SP_DEVICE_INTERFACE_DATA
Public cbSize As UInteger
Public InterfaceClassGuid As Guid
Public Flags As UInteger
Public Reserved As IntPtr
End Structure
<StructLayout(LayoutKind.Sequential, Pack:=1)> _
Public Structure SP_DEVICE_INTERFACE_DETAIL_DATA
Public cbSize As UInteger
<MarshalAs(UnmanagedType.ByValTStr, SizeConst:=256)> Public DevicePath As String
End Structure
<DllImport("setupapi.dll",
CharSet:=CharSet.Auto,
SetLastError:=True)> _
Public Shared Function SetupDiGetDeviceInterfaceDetail(ByVal hDevInfo As IntPtr,
ByRef deviceInterfaceData As SP_DEVICE_INTERFACE_DATA,
ByRef deviceInterfaceDetailData As SP_DEVICE_INTERFACE_DETAIL_DATA,
ByVal deviceInterfaceDetailDataSize As Int32,
ByRef RequiredSize As Int32,
ByRef deviceInfo As SP_DEVINFO_DATA) As Boolean
Thanks very much in advance,
If Not SetupDiGetDeviceInterfaceDetail(..., Nothing, 0, StructureSize, Nothing)
You are struggling with basic VB.NET usage here, the keyword Nothing doesn't mean what you hope it does. What the api function wants you to do is pass a null pointer. IntPtr.Zero. That could be Nothing, but it is not in this case. You declared the argument types as structures. They are value types. Nothing means something else in the case of value types, it means "default value". So you are actually passing a pointer to a structure here, a structure that's zero initialized. The function is unhappy about that and tells you so.
You cannot pass IntPtr.Zero when you use these pinvoke declarations. You can cheat and declare an overload of the function, one that uses different argument types. Like this:
<DllImport("setupapi.dll", CharSet:=CharSet.Auto, SetLastError:=True)> _
Public Shared Function SetupDiGetDeviceInterfaceDetail(
ByVal hDevInfo As IntPtr,
ByRef deviceInterfaceData As SP_DEVICE_INTERFACE_DATA,
ByVal mustPassIntPtrZero As IntPtr,
ByVal mustPassZero As Int32,
ByRef RequiredSize As Int32,
ByVal mustPassIntPtrZero2 As IntPtr) As Boolean
Now you can make the first call to the function:
If Not SetupDiGetDeviceInterfaceDetail(..., IntPtr.Zero, 0, StructureSize, IntPtr.Zero)
You should use the returned RequiredSize to allocate memory with Marshal.AllocHGlobal(). Pass the returned pointer as the deviceInterfaceDetailData argument. Which must now be declared as ByVal IntPtr. And use Marshal.PtrToStructure() to convert it to the struct after the call. Yes, Pack is an issue on 64-bit operating systems.
Fix the string problem by declaring the structure with CharSet:=CharSet.Auto so you'll get the Unicode conversion instead of the Ansi conversion.
Now I figured out, in fact half working :) Maybe someone can en light me.
At least on VB .NET 2010, the SetupDiGetDeviceInterfaceDetail() doesn't line NULLs (aka nothing) as parameters, so I had to first pass something dummy:
DetailedInterfaceDataStructure.cbSize = 6
SetupDiGetDeviceInterfaceDetail(DeviceInfoTable, InterfaceDataStructure, DetailedInterfaceDataStructure, 1000, StructureSize, DevInfoData)
The cbSize must be <> 0 and on 32 bit systems must be 6 (4 for DWORD and 2 for null terminated wide string). The 1000 it's just a big number, so SetupDiGetDeviceInterfaceDetail() won't return even ERROR_INSUFFICIENT_BUFFER
Now the second pass supposed to get the real stuff:
SetupDiGetDeviceInterfaceDetail(DeviceInfoTable, InterfaceDataStructure, DetailedInterfaceDataStructure, StructureSize, StructureSize, Nothing)
Which doesn't return any system error, but path is only a backslash character "\" and this isn't good.... it supposed to be a valid USB path
"\\?\usb#vid_01E5&pid_00A2#5&1d4952dc&0&2#{18d0A210-85D2-........"
Anyone can help me ?...
yes I am struggling with VB since I'm coming from pure C99 and assembler for MCU :) In fact, after few hours of errors I got some snippet in C# that worked like a charm but I had to know why isn't working in VB :) I finally solved, partially like your advices
I overloaded SetupDiGetDeviceInterfaceDetail() with another one, just to accept a ptr to some unmanaged buffer:
<DllImport("setupapi.dll",
CharSet:=CharSet.Auto,
SetLastError:=True)> _
Public Shared Function SetupDiGetDeviceInterfaceDetail(ByVal hDevInfo As IntPtr,
ByRef deviceInterfaceData As SP_DEVICE_INTERFACE_DATA,
ByVal deviceInterfaceDetailData As IntPtr,
ByVal deviceInterfaceDetailDataSize As Int32,
ByRef RequiredSize As Int32,
ByRef deviceInfo As SP_DEVINFO_DATA) As Boolean
End Function
Now the problem was I used ByRef deviceInterfaceDetailData As IntPtr instead of ByVal deviceInterfaceDetailData As IntPtr, was my mistake from VB's quite simple perspective.
Indeed, now I can pass some unmanaged buffer at 2'nd call of SetupDiGetDeviceInterfaceDetail()
Dim PUnmanagedDetailedInterfaceDataStructure As IntPtr = IntPtr.Zero
PUnmanagedDetailedInterfaceDataStructure = Marshal.AllocHGlobal(StructureSize)
DetailedInterfaceDataStructure.cbSize = 6 ' cbSize = 4 bytes for DWORD + 2 bytes for Unicode null terminator
Marshal.StructureToPtr(DetailedInterfaceDataStructure, PUnmanagedDetailedInterfaceDataStructure, False) ' Copy the contents of the structure, to an unmanaged memory space
SetupDiGetDeviceInterfaceDetail(DeviceInfoTable, InterfaceDataStructure, PUnmanagedDetailedInterfaceDataStructure, StructureSize, StructureSize, DevInfoData)
And this is finally working. I expect some points for struggling :)
I saw a lot of similar threads on internet but none with a complete solve.
Merry Christmas to all
Currently, Smart Indent in VS 2010 is giving me lots of headaches. Everytime I try to put each parameter of a Sub or a Function into one line like
Private Function Foo (ByVal a As String, _
ByVal i As Integer)
I get things like
Private Function Foo (ByVal a As String, _
ByVal i As Integer)
Can you point me to some extension/add-on/configuration to pretty print VB code?
Did you try this?
Private Function Foo( _
ByVal a As String, _
ByVal i As Integer)
Breaking the line before the first parameter puts each of your parameters on a seperate line and treats them all identically rather than making an exception (not a pun) of the first one.
I have code that needs to run on both Excel 2003 and Excel 2007, and there are a few spots where changes in the versions cause the code to halt. I tried separating these lines out with If-Else statements, but the code won't compile on either because it doesn't recognize the code used for the other. Is there any way I could tell one version to ignore a block of code, similar to a C or C++-style #ifdef, in VBA?
This is a good starting point, but it won't work with the version of Excel that its running on, since that can only be figured out at run-time, not compile time.
If you need to branch your code based on information only discoverable at run time you might consider late binding as a solution. There are two ways you can sneak around version problems.
The first way can be used if you need to Access a property or method that only exists in certain versions, you can use CallByName. The advantage of call by name is that it allows you to preserve early binding (and intellisense) for your objects as much as possible.
To give an example, Excel 2007 has a new TintAndShade property. If you wanted to change the color of a range, and for Excel 2007 also ensure TintAndShade was set to 0 you would run into trouble because your code won't compile in Excel 2003 which does not have TintAndShade as a property of the range object. If you access the property that you know is not in all versions using CallByName, you code will compile in all versions fine, but only run in the versions you specify. See below:
Sub Test()
ColorRange Selection, Excel.Application.version, 6
End Sub
Sub ColorRange(rng As Excel.Range, version As Double, ParamArray args() As Variant)
With rng.Interior
.colorIndex = 6
.Pattern = xlSolid
If version >= 12# Then
'Because the property name is stored in a string this will still compile.
'And it will only get called if the correct version is in use.
CallByName rng.Interior, "TintAndShade", VbLet, 0
End If
End With
End Sub
The second way is for classes that have to be instantiated via "New" and don't even exist in old versions. You won't run into this problem with Excel, but I will give a quickie demo so you can see what I mean:
Imagine that you wanted to do File IO, and for some bizarre reason not all of the computers had the Microsoft Scripting Runtime on them. But for some equally bizarre reason you wanted to make sure it was used whenever it was available. If set a reference to it and use early binding in your code, the code won't compile on systems that don't have the file. So you use late binding instead:
Public Sub test()
Dim strMyString As String
Dim strMyPath As String
strMyPath = "C:\Test\Junk.txt"
strMyString = "Foo"
If LenB(Dir("C:\Windows\System32\scrrun.dll")) Then
WriteString strMyPath, strMyString
Else
WriteStringNative strMyPath, strMyString
End If
End Sub
Public Sub WriteString(ByVal path As String, ByVal value As String)
Dim fso As Object '<-Use generic object
'This is late binding:
Set fso = CreateObject("Scripting.FileSystemObject")
fso.CreateTextFile(path, True, False).Write value
End Sub
Public Sub WriteStringNative(ByVal path As String, ByVal value As String)
Dim lngFileNum As Long
lngFileNum = FreeFile
If LenB(Dir(path)) Then Kill path
Open path For Binary Access Write Lock Read Write As #lngFileNum
Put #lngFileNum, , value
Close #lngFileNum
End Sub
There is a comprehensive list of all Adds and Changes to Excel Object Model since 2003:
http://msdn.microsoft.com/en-us/library/bb149069.aspx
For changes between 1997 and 2000 go here:
http://msdn.microsoft.com/en-us/library/aa140068(office.10).aspx
Yes it is possible to do conditional compilation in Excel VBA. Below is a brief resource and some example code:
Conditional Compilation
#If Win32 Then
' Profile String functions:
Private Declare Function WritePrivateProfileString Lib "KERNEL32" Alias "WritePrivateProfileStringA" (ByVal lpApplicationName As String, ByVal lpKeyName As Any, ByVal lpString As Any, ByVal lpFileName As String) As Long
Private Declare Function GetPrivateProfileString Lib "KERNEL32" Alias "GetPrivateProfileStringA" (ByVal lpApplicationName As Any, ByVal lpKeyName As Any, ByVal lpDefault As Any, ByVal lpReturnedString As String, ByVal nSize As Long, ByVal lpFileName As String) As Long
#Else
' Profile String functions:
Private Declare Function WritePrivateProfileString Lib "Kernel" (ByVal lpApplicationName As String, ByVal lpKeyName As Any, ByVal lpString As Any, ByVal lpFileName As String) As Integer
Private Declare Function GetPrivateProfileString Lib "Kernel" (ByVal lpApplicationName As String, ByVal lpKeyName As Any, ByVal lpDefault As Any, ByVal lpReturnedString As String, ByVal nSize As Integer, ByVal lpFileName As String) As Integer
#End If
Can you post the offending lines of code?
If it is a constant like vbYes or xlFileFormat or whatever, use the corresponding numeric value.
Show me what you got, I'll see if I can refactor it.
Bill