How do I programmatically update OCX references in vb6 projects? - vb6

I periodically break binary compatibility and need to recompile an entire vb6 application composed of several dozen ActiveX DLLs and OCXs in total. I've written a script to automate this process, but I have encountered a problem.
When an OCX is recompiled with project compatibility its version is incremented, and projects referencing the OCX will not recompile until their reference is updated to the new version. This is checked automatically when the project is opened normally, and the user is prompted to update the reference, but I need to do it in a script.
How do I do it?

We are doing similar things, i.e. manipulating the references to the used OCXs directly in VB6 .vbp files, in our VB6 Project References Update Tool (download here). Generally it is used to update the references when the used ActiveX change their version numbers, CLSIDs, etc.
The tools is open-source so everyone who is interested in this problem can borrow our VB code snippets to implement tasks like these.
Our tool is written in Visual Basic 6 and uses tlbinf32.dll (the TypeLib Information DLL) that allows you to programmatically extract information from type libraries.

My project, maintained over a decade, consists of a hierarchy of two dozen ActiveX DLLs and a half dozen controls. Compiled with a script system as well.
I don't recommend doing what you are doing.
What we do is as follows
Make our changes including additions
and test in the IDE.
We compile from the bottom of the
hierarchy to the top
we copy the newly complied files to
a revision directory for example
601,then 602 etc etc
we create the setup.exe
when the setup is finalized we copy
over the revision directory into our
compatibility director. Note we
never point to the compiled binary
in the project directory. Always to
a compability directory that has all
the DLLs.
The reason this works is that if you look at the IDL source using the OLE View tool you will find that any referenced control or dlls is added to the interface via a #include. If you point to the binary in your project directory then the include is picked up from the registry which can lead to a lot of strangness and compatibility.
However if the referenced DLL is present in the directory that binary exists while being used for binary compatibility, VB6 will use that instead of whatever in the registry.
Now there is one problem that you will get on an infrequent basis. Consider this heirarchy
MyUtilityDLL
MyObjectDLL
MyUIDLL
MyEXE
If you ADD a property or method to a class in MyUtilityDLL MyUIDLL may not compile giving a binary incompatibility error if you are lucky or a strange error like [inref]. In any case the solution is to compile MyUtilityDLL and then immediately copy MyUtilityDLL into the compatibility directory. Then the rest of the automated compile will work fine.
You may want to include this step in the automated build.
Note that in many cases the projects will work fine in the IDE. To if you are now aware of this you could be pulling your hair out.

I guess you would have to edit the project files (.vbp), Form files (.frm) and the control files (.ctl) that reference the DLLs and OCXs and increment the typelib version number.
You would find latest typelib version number for the control / DLL in the registry.
This could be a pain depending on how many files you have.
A hack would be to open main project with VB6 using your script and send keys to confirm the Update References and then save the project.
Good Luck

Self-answer: I have written some vb6 code to do the upgrade programmatically. It is not extensively tested, there are probably a few bugs here and there for corner cases, but I did use it successfully.
Option Explicit
Const HKEY_LOCAL_MACHINE As Long = &H80000002
Const KEY_ENUMERATE_SUB_KEYS As Long = 8
Private Declare Function RegOpenKeyEx Lib "advapi32.dll" Alias "RegOpenKeyExA" (ByVal hKey As Long, ByVal lpSubKey As String, ByVal ulOptions As Long, ByVal samDesired As Long, phkResult As Long) As Long
Private Declare Function RegEnumKeyEx Lib "advapi32.dll" Alias "RegEnumKeyExA" (ByVal hKey As Long, ByVal dwIndex As Long, ByVal lpName As String, lpcbName As Long, lpReserved As Long, ByVal lpClass As String, lpcbClass As Long, lpftLastWriteTime As Any) As Long
Private Declare Function RegCloseKey Lib "advapi32.dll" (ByVal hKey As Long) As Long
'''Returns the expected major version of a GUID if it exists, and otherwise returns the highest registered major version.
Public Function GetOcxMajorVersion(ByVal guid As String, Optional ByVal expected_version As Long) As Long
Const BUFFER_SIZE As Long = 255
Dim reg_key As Long
Dim ret As Long
Dim enum_index As Long
Dim max_version As Long: max_version = -1
ret = RegOpenKeyEx(HKEY_LOCAL_MACHINE, "SOFTWARE\Classes\TypeLib\{" & guid & "}", 0, KEY_ENUMERATE_SUB_KEYS, reg_key)
If ret <> 0 Then Err.Raise ret, , "Failed to open registry key."
Do
'Store next subkey name in buffer
Dim buffer As String: buffer = Space(BUFFER_SIZE)
Dim cur_buffer_size As Long: cur_buffer_size = BUFFER_SIZE
ret = RegEnumKeyEx(reg_key, enum_index, buffer, cur_buffer_size, ByVal 0&, vbNullString, ByVal 0&, ByVal 0&)
If ret <> 0 Then Exit Do
buffer = Left(buffer, cur_buffer_size)
'Keep most likely version
buffer = Split(buffer, ".")(0)
If Not buffer Like "*[!0-9A-B]*" And Len(buffer) < 4 Then
Dim v As Long: v = CLng("&H" & buffer) 'convert from hex
If v = expected_version Then
max_version = v
Exit Do
ElseIf max_version < v Then
max_version = v
End If
End If
enum_index = enum_index + 1
Loop
RegCloseKey reg_key
If max_version = -1 Then Err.Raise -1, , "Failed to enumerate any viable subkeys."
GetOcxMajorVersion = max_version
End Function
Public Function RemoveFilename(ByVal path As String) As String
Dim folders() As String: folders = Split(Replace(path, "/", "\"), "\")
RemoveFilename = Left(path, Len(path) - Len(folders(UBound(folders))))
End Function
'''Changes any invalid OCX references to newer registered version
Public Sub UpdateFileOCXReferences(ByVal path As String)
Dim file_data As String
Dim changes_made As Boolean
'Read
Dim fn As Long: fn = FreeFile
Open path For Input As fn
While Not EOF(fn)
Dim line As String
Line Input #fn, line
'check for ocx reference line
If LCase(line) Like "object*=*{*-*-*-*-*}[#]*#.#*[#]#*;*.ocx*" Then
'get guid
Dim guid_start As Long: guid_start = InStr(line, "{") + 1
Dim guid_end As Long: guid_end = InStr(line, "}")
Dim guid As String: guid = Mid(line, guid_start, guid_end - guid_start)
'get reference major version
Dim version_start As Long: version_start = InStr(line, "#") + 1
Dim version_end As Long: version_end = InStr(version_start + 1, line, ".")
Dim version_text As String: version_text = Mid(line, version_start, version_end - version_start)
'play it safe
If Len(guid) <> 32 + 4 Then Err.Raise -1, , "GUID has unexpected length."
If Len(version_text) > 4 Then Err.Raise -1, , "Major version is larger than expected."
If guid Like "*[!0-9A-F-]*" Then Err.Raise -1, , "GUID has unexpected characters."
If version_text Like "*[!0-9]*" Then Err.Raise -1, , "Major version isn't an integer."
'get registry major version
Dim ref_version As Long: ref_version = CLng(version_text)
Dim reg_version As Long: reg_version = GetOcxMajorVersion(guid, ref_version)
'change line if necessary
If reg_version < ref_version Then
Err.Raise -1, , "Registered version precedes referenced version."
ElseIf reg_version > ref_version Then
line = Left(line, version_start - 1) & CStr(reg_version) & Mid(line, version_end)
changes_made = True
End If
End If
file_data = file_data & line & vbNewLine
Wend
Close fn
'Write
If changes_made Then
Kill path
Open path For Binary As fn
Put fn, , file_data
Close fn
End If
End Sub
'''Changes any invalid in included files to newer registered version
Public Sub UpdateSubFileOCXReferences(ByVal path As String)
Dim folder As String: folder = RemoveFilename(path)
Dim fn As Long: fn = FreeFile
Open path For Input As fn
While Not EOF(fn)
Dim line As String
Line Input #fn, line
If LCase(line) Like "form=*.frm" _
Or LCase(line) Like "usercontrol=*.ctl" Then
Dim file As String: file = folder & Mid(line, InStr(line, "=") + 1)
If Dir(file) <> "" Then
UpdateFileOCXReferences file
End If
End If
Wend
Close fn
End Sub

Related

VB6 - How to open Chrome?

The problem seems to be simple, but all solutions that I found doesn't worked.
I would like to know how to open Chrome automatically, I tried the code below and several other solutions, but when Chrome is completely closed and I try to open, it shows the window, but it does not open any website.
Private Sub Command1_Click()
Shell "C:\Program Files\Google\Chrome\Application\chrome.exe -url https://www.google.com/"
End Sub
This doesn't have anything to do with VB6, of course, it's about Chrome command line switches. Chrome command line switches start with two dashes. So this should work:
Shell "C:\Program Files\Google\Chrome\Application\chrome.exe --url https://www.google.com/"
(Program Files (x86) if you're running 64 bit Windows, naturally)
But you don't need to specify the switch for the url, this also works:
Shell "C:\Program Files\Google\Chrome\Application\chrome.exe https://www.google.com/"
EDIT:
It would actually appear to be that there isn't an 'url' switch for Chrome, so it is proper to simply place the url on the command line on it's own, as in my second shell command above.
You should ensure where Chrome.exe is installed. 32 or 64 bit?
You have to check before to run it.
For example, I have a 64-bit computer and Chrome.exe is 32 bit version installed on:
C:\Program Files (x86)\Google\Chrome\Application
The code below check for both 32/64 bit version:
open a new VBP project, on Form1:
add a CommandButton, Name: cmdOpenChrome
add a TextBox, Name: txtUrl
copy the below code:
Option Explicit
Private Declare Function ShellExecute Lib "shell32.dll" Alias "ShellExecuteA" ( _
ByVal hwnd As Long, _
ByVal lpOperation As String, _
ByVal lpFile As String, _
ByVal lpParameters As String, _
ByVal lpDirectory As String, _
ByVal nShowCmd As Long) As Long
Public Sub OpenChrome(ByVal pURL As String)
Dim sChromePath As String
Dim sTmp As String
Dim sProgramFiles As String
Dim bNotFound As Boolean
'
' check for 32/64 bit version
'
sProgramFiles = Environ("ProgramFiles")
sChromePath = sProgramFiles & "\Google\Chrome\Application\chrome.exe"
If Dir$(sChromePath) = vbNullString Then
' if not found, search for 32bit version
sProgramFiles = Environ("ProgramFiles(x86)")
If sProgramFiles > vbNullString Then
sChromePath = sProgramFiles & "\Google\Chrome\Application\chrome.exe"
If Dir$(sChromePath) = vbNullString Then
bNotFound = True
End If
Else
bNotFound = True
End If
End If
If bNotFound = True Then
MsgBox "Chrome.exe not found"
Exit Sub
End If
ShellExecute 0, "open", sChromePath, pURL, vbNullString, 1
End Sub
Private Sub cmdOpenChrome_Click()
OpenChrome txtUrl.Text
End Sub
The sample below use many different browsers:
http://nuke.vbcorner.net/Projects/VB60/VB60variousprojects/tabid/79/language/en-US/Default.aspx#OpenURLwithAnyBrowser

VB6: ShellExecute an EXE inside AppData

I have the following sub:
Public Sub ShellApp(URL As String)
Dim vResult As Long
vResult = ShellExecute(0, "open", URL, vbNullString, vbNullString, vbMinimizedFocus)
End If
End Sub
This is on a layer that cannot be changed due to several functionality needed on that sub.
Now, on our Main() sub for example, we check a list of added plugins saved in a text file beside the EXE, and call the above Sub in for loop with the path of the plugins to run them. So if I have 3 plugins as below in the text file:
C:\App1.EXE
C:\App2.EXE
C:\Users\AhmadMusa\AppData\Roaming\App3.exe
First two apps will run fine on all PCs (Static path), but third app will not work on any PC except mine which is not ok... Note that App3 always installed on AppData on any PC, so how to dynamically access it to run the app from any PC without adjustments on the sub.
What will be the path placed in the text file for third app so it can work on all PCs?
I tried (AppData\Roaming\App3.exe) but it does not work... I found on a thread (http://www.vbforums.com/showthread.php?529776-RESOLVED-Open-a-folder-in-AppData) that I can call something like (shell:AppData\Roaming\App3.exe) it did not work to run the App3.exe, but if I call (shell:AppData\Roaming) it will open the Roaming folder very well. But cannot run the EXE.
Any ideas ?
Thanks.
I believe that there is no way to solve the problem without altering the original procedure "ShellApp".
in case you change your mind, i think this post may come in help (with some tweekings)
Public Sub ShellApp(URL As String)
Dim vResult As Long
'vResult = ShellExecute(0, vbNullString, URL, vbNullString, vbNullString, vbNormalFocus)
vResult = ShellExecute(0, vbNullString, "cmd.exe", "/k """"" & URL & """""", vbNullString, vbNormalFocus)
End Sub
Private Sub Command1_Click()
ShellApp "%appdata%\PROGRAME.exe"
End Sub
this because only "cmd.exe" and ofcourse batch scripts are able to expend variables that are enclosed with percent character "%"
To close the console as soon as it starts change the parameter "/k" to "/c",
sub test()
dim tmp as string
tmp = environ("AppData ") & "\calc.exe"
call ShellExecute(..., tmp, ...)
end sub
fill the other arguments (...) the way you see it right
You need to expand the Environment variable (this is what the %...% does):
Debug.Print Environ("APPDATA") ' will print the expanded %appdata%
So, in your text file you should put:
%APPDATA%\App3.exe
How to expand the path? You can loop over the environment variables provided by the VB Environ function and do a string replace by yourself (the VB way) or you can profit from the ExpandEnvironmentStrings function (the Win32 API way).
Below a snippet using this second option:
Private Declare Function ExpandEnvironmentStrings Lib "kernel32.dll" _
Alias "ExpandEnvironmentStringsA" ( _
ByVal lpSrc As String, _
ByVal lpDst As String, _
ByVal nSize As Long) As Long
Public Function ExpandEnvironString(ByVal URL As String) As String
Dim buf As String, bufSize As Long
bufSize = ExpandEnvironmentStrings(URL, ByVal 0&, 0&)
buf = String(bufSize + 1, vbNullChar)
bufSize = ExpandEnvironmentStrings(URL, buf, Len(buf))
ExpandEnvironString = Left$(buf, InStr(1, buf, vbNullChar) - 1)
End Function
Before you call ShellApp(URL As String) you should expand the path:
URL = ExpandEnvironString(URL)
ShellExecute will receive the expanded path: C:\Users\AhmadMusa\AppData\Roaming\App3.exe
This is a non-breaking change, because if your initial default setting will be later changed to a custom fixed path, the ExpandEnvironmentStrings function will simply ignore it.
Example:
ExpandEnvironString("C:\App1.EXE ") will return C:\App1.EXE
More info:
you can get all your environment variables with following procedure:
Private Sub EnvironmentEntries()
Dim Entry As String, i As Long
i = 1
Do
Entry = Environ(i)
i = i + 1
If Entry = "" Then Exit Do
Debug.Print Entry
Loop
End Sub
... and check some additional info here:
Why are there directories called Local, LocalLow, and Roaming under \Users\?

Form type "file" default folder

I have a webform displayed in a WebBrowser control in a Visual Basic application to allow users to upload files to my webserver. Unusually for this type of thing, I know in advance the folder they need to browse to (I want them to upload a file which the VB application has generated) and I would like the Browse dialog box to default to that folder but it seems to default to whichever folder was last used by any application for File / Open.
I've tried using ChDir in VB to set the current folder but that doesn't work.
Is there any way I can persuade the Browse box to default to my desired folder?
First of all, I ought to state that there is no reliable way of doing this. The reason why it is so hard is for security reasons. There is a defaultValue and value property for the INPUT TYPE=FILE element, but if programmers had access to this, this could be used to suck files from the client machine - definitely not a good idea.
In Internet Explorer, the browse file dialogue is actually implemented by a Windows Common Dialog component. However, you have no direct access to this component.
There is no reliable browser-independent way of doing this. And I certainly don't recommend you reverse-engineer the "Last Recently Used" file list for the Common Dialog control (see jac's link). Doing that is very dangerous, since it is an internal-algorithm likely to change. And worse, you are hacking global state to solve a local problem (see Old New Thing blog, ad nauseam).
A solution that doesn't violate global state, but still hacky is to take advantage of the fact we know what the text is on the file upload dialogue. After your document has loaded, you can use a Timer to wait for the dialogue to appear, and at that point, paste the correct directory into the dialogue.
In my sample, I have code where the web browser control sits, and the BrowserHack.bas module.
Form code:
Option Explicit
Private Sub cmdLoadPage_Click()
' Store the default path into the .Tag property.
tmrWaitForDialogue.Tag = "C:\Windows\System32"
' Load URL.
wbMain.Navigate "<URL>"
End Sub
Private Sub Form_Load()
tmrWaitForDialogue.Enabled = False
tmrWaitForDialogue.Interval = 100 ' 10th of a second delay.
End Sub
Private Sub tmrWaitForDialogue_Timer()
If BrowserHack.IsBrowserFileDialogueVisible Then
' We don't want the Timer to fire again.
tmrWaitForDialogue.Enabled = False
' Copy the directory onto the clipboard.
Clipboard.Clear
Clipboard.SetText tmrWaitForDialogue.Tag
' The focus will be on the file path text box in the Open dialogue.
' Use CTL-V to paste the text, and then followed by an Enter character to
' dismiss the dropdown, and another to Open the folder.
SendKeys "^V"
SendKeys "{ENTER}"
SendKeys "{ENTER}"
End If
End Sub
Private Sub wbMain_DocumentComplete(ByVal pDisp As Object, URL As Variant)
tmrWaitForDialogue.Enabled = True
End Sub
BrowserHack:
' Purpose: Code to look for the WebBrowser (Internet Explorer) dialogue which appears when a File Upload control is clicked.
' Notes: Internet Explorer version dependent.
Option Explicit
Private Declare Function EnumThreadWindows Lib "User32.dll" ( _
ByVal dwThreadId As Long, _
ByVal lpfn As Long, _
ByVal lParam As Long _
) As Long
Private Declare Function GetClassName Lib "User32.dll" Alias "GetClassNameW" (ByVal hWnd As Long, ByVal lpClassName As Long, ByVal nMaxCount As Long) As Long
Private Declare Function GetWindowText Lib "User32.dll" Alias "GetWindowTextW" (ByVal hWnd As Long, ByVal lpString As Long, ByVal nMaxCount As Long) As Long
Private Declare Function IsWindowVisible Lib "User32.dll" (ByVal hWnd As Long) As Long
Private Const APITRUE As Long = 1 ' Win32 API TRUE value
Private Const APIFALSE As Long = 0 ' Win32 API FALSE value
' This dialogue class is pretty universal.
Private Const m_ksDialogueClass As String = "#32770"
Private Const m_knDialogueClassLen As Long = 6
' This text may well change every time the browser is updated.
Private Const m_ksDialogueText As String = "Choose File to Upload"
Private Const m_knDialogueTextLen As Long = 21
' Buffers to be used for these strings.
Private m_sClassNameBuffer As String
Private m_sWindowNameBuffer As String
' Callback from the EnumThreadWindow() function.
Private Function EnumThreadWndProc( _
ByVal hWnd As Long, _
ByVal lParam As Long _
) As Long
Dim nRet As Long
' Filter out hidden windows.
If IsWindowVisible(hWnd) = APITRUE Then
' Retrieve the class name of the window.
' Note that this function requires you to allocate a buffer *including* the terminating null character.
' Since VB strings *always* are null terminated, we can add one to the string length.
nRet = GetClassName(hWnd, StrPtr(m_sClassNameBuffer), (m_knDialogueClassLen + 1))
' If the classes match, then try for a match on the window's text.
If m_sClassNameBuffer = m_ksDialogueClass Then
' Ditto GetClassName().
nRet = GetWindowText(hWnd, StrPtr(m_sWindowNameBuffer), (m_knDialogueTextLen + 1))
If m_sWindowNameBuffer = m_ksDialogueText Then
' This return value says "stop the enumeration".
' In this case EnumThreadWindow() with also return APIFALSE.
EnumThreadWndProc = APIFALSE
Exit Function
End If
End If
End If
EnumThreadWndProc = APITRUE
End Function
' Purpose: If the browser window is detected
Public Function IsBrowserFileDialogueVisible() As Boolean
' If this is the first time this function has been called, the buffers will not be allocated.
' Do this now.
If LenB(m_sClassNameBuffer) = 0 Then
m_sClassNameBuffer = Space$(m_knDialogueClassLen)
m_sWindowNameBuffer = Space$(m_knDialogueTextLen)
End If
' Enumerate through all windows on this thread. VB apps are single-threaded, and all GUI elements are forced to be on this thread, so this is ok.
If EnumThreadWindows(App.ThreadID, AddressOf EnumThreadWndProc, 0&) = APIFALSE Then
IsBrowserFileDialogueVisible = True
Else
IsBrowserFileDialogueVisible = False
End If
End Function

Printer Page Size Problem

I am trying to set a custom paper size by doing:
Printer.Height = 2160
Printer.Width = 11900
But it doesn't seen to have any effect. After setting this up, i ask for that values and it returns the default ones. And this:
Printer.PaperSize = 256
Returns an error...
Any ideas??
Either your printer doesn't allow these properties to be set, or you're exceeding their maximum allowed values. From the Visual Basic Reference
If you set the Height and Width
properties for a printer driver that
doesn't allow these properties to be
set, no error occurs and the size of
the paper remains as it was. If you
set Height and Width for a printer
driver that allows only certain values
to be specified, no error occurs and
the property is set to whatever the
driver allows. For example, you could
set Height to 150 and the driver would
set it to 144.
I don't know why you're getting an error when you set the Papersize property to 256. It works for me. Also, the documentation states, "Setting a printer's Height or Width property automatically sets PaperSize to vbPRPSUser.", which equals 256.
I was actually involved with the same problem but I just happen to find a breakthrough.
First you need to create a custom form that defines you custom paper size. Then, you need to
refer to Windows API to check the form name you've just created. You'll get the for name
from an array returned from a function and use the array index where the form name was found.
Finally use it as the value for printer.papersize
Example below:
Public Type PRINTER_DEFAULTS
pDatatype As Long
pDevMode As Long
DesiredAccess As Long
End Type
Public Type FORM_INFO_1
Flags As Long
pName As Long ' String
Size As SIZEL
ImageableArea As RECTL
End Type
Public Declare Function EnumForms Lib "winspool.drv" Alias "EnumFormsA" _
(ByVal hPrinter As Long, ByVal Level As Long, ByRef pForm As Any, _
ByVal cbBuf As Long, ByRef pcbNeeded As Long, _
ByRef pcReturned As Long) As Long
Public Declare Sub CopyMemory Lib "KERNEL32" Alias "RtlMoveMemory" _
(pDest As Any, pSource As Any, ByVal cbLength As Long)
Public Declare Sub Sleep Lib "KERNEL32" (ByVal dwMilliseconds As Long)
Public Declare Function OpenPrinter Lib "winspool.drv" Alias _
"OpenPrinterA" (ByVal pPrinterName As String, phPrinter As Long, _
pDefault As PRINTER_DEFAULTS) As Long
Public Declare Function ClosePrinter Lib "winspool.drv" _
(ByVal hPrinter As Long) As Long
Public Declare Function lstrcpy Lib "KERNEL32" Alias "lstrcpyA" _
(ByVal lpString1 As String, ByRef lpString2 As Long) As Long
'UDF
Public Function PtrCtoVbString(ByVal Add As Long) As String
Dim sTemp As String * 512, x As Long
x = lstrcpy(sTemp, ByVal Add)
If (InStr(1, sTemp, Chr(0)) = 0) Then
PtrCtoVbString = ""
Else
PtrCtoVbString = Left(sTemp, InStr(1, sTemp, Chr(0)) - 1)
End If
End Function
Public Function IsFormExist(ByVal DeviceName As String, ByVal isFormName As String, ByVal PrinterHandle As Long) As Long
Dim NumForms As Long, i As Long
Dim FI1 As FORM_INFO_1
Dim pd As PRINTER_DEFAULTS
Dim aFI1() As FORM_INFO_1 ' Working FI1 array
Dim Temp() As Byte ' Temp FI1 array
Dim FormIndex As Integer
Dim BytesNeeded As Long
Dim RetVal As Long
On Error GoTo cleanup
FormIndex = 0
ReDim aFI1(1)
' First call retrieves the BytesNeeded.
RetVal = OpenPrinter(DeviceName, PrinterHandle, pd)
If (RetVal = 0) Or (PrinterHandle = 0) Then
'Can't access current printer. Bail out doing nothing
Exit Function
End If
RetVal = EnumForms(PrinterHandle, 1, aFI1(0), 0&, BytesNeeded, NumForms)
ReDim Temp(BytesNeeded)
ReDim aFI1(BytesNeeded / Len(FI1))
' Second call actually enumerates the supported forms.
RetVal = EnumForms(PrinterHandle, 1, Temp(0), BytesNeeded, BytesNeeded, _
NumForms)
Call CopyMemory(aFI1(0), Temp(0), BytesNeeded)
For i = 0 To NumForms - 1
With aFI1(i)
If isFormName = PtrCtoVbString(.pName) Then
' Found the desired form
FormIndex = i + 1
Exit For
End If
End With
Next i
IsFormExist = FormIndex ' Returns the number when form is found.
cleanup:
'Release the printer handle
If (PrinterHandle <> 0) Then Call ClosePrinter(PrinterHandle)
End Function
'Here We Go
dim papercode as long, printername as string, formname as string
printername=printer.Devicename
formname = "myform"
papercode=IsFormExist(printername, formname, Printer.hdc)
if papercode<>0 then
printer.papersize=papercode
end if
Give it a try, good luck
Are you sure the error isn't related to the maximum print width of the printer itself? Many printers have a max print width of 8.25" (11880) to allow 1/4" margins on either side of a 8.5" wide paper.
Quickest way to check would be to simply set the print wide to 11880 or lower and see if it works.
Another possibility would be permissions to the printer. If it's a shared network resource it may be locked down.
The solution is to use windows 98. It does not work with win2k, neither winXP. The same code, the same printer.
Regards.
I'm testing this code, but I can not see the custom form I created using printers and scanners in the Control Panel Windows XP Professional SP3.
Note: I could check in regedit that this form exists and its ID is 512 in a string value and it contains the name of the form created in the printers control panel.
Why this function does not return my custom form, I am using an HP Laserjet 1020.

using ini file in vb6, problem with path to file

I have read many articles about how to use an INI file within my VB6 project. I don't have a problem with the methods, my problem is how to make the EXE file find the INI file. I don't want to hard code the path in the program. I simply want the EXE to expect the INI file to be present in the same folder the EXE is executed from.
When I run the program from inside VB6 IDE, the INI is found and processed. When I compile the program and run the EXE, nothing is found.
My code looks like:
gServer = sGetINI(sINIFile, "TOOLBOM", "ServerName", "?")
where TOOLBOM is the [Section] and "ServerName" is the key for the value.
I obtained the following code for the API:
Rem API DECLARATIONS
Declare Function GetPrivateProfileString Lib "kernel32" Alias _
"GetPrivateProfileStringA" (ByVal lpApplicationName _
As String, ByVal lpKeyName As Any, ByVal lpDefault _
As String, ByVal lpReturnedString As String, ByVal _
nSize As Long, ByVal lpFileName As String) As Long
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
Public Function sGetINI(sINIFile As String, sSection As String, sKey _
As String, sDefault As String) As String
Dim sTemp As String * 256
Dim nLength As Integer
sTemp = Space$(256)
nLength = GetPrivateProfileString(sSection, sKey, sDefault, sTemp, _
255, sINIFile)
sGetINI = Left$(sTemp, nLength)
End Function
Public Sub writeINI(sINIFile As String, sSection As String, sKey _
As String, sValue As String)
Dim n As Integer
Dim sTemp As String
sTemp = sValue
Rem Replace any CR/LF characters with spaces
For n = 1 To Len(sValue)
If Mid$(sValue, n, 1) = vbCr Or Mid$(sValue, n, 1) = vbLf _
Then Mid$(sValue, n) = " "
Next n
n = WritePrivateProfileString(sSection, sKey, sTemp, sINIFile)
End Sub
In VB6 you can use the App.Path to specify the path to files that should be in the directory you are executing from. e.g.
sIniFile = App.Path & "\myIniFile.ini"
What is the error that you're getting?
Bob is right, this will fail as soon as anyone tries to run it on Vista or later. Writeable data files are not supposed to go in Program Files. Windows now enforces these rules. Global settings for all users belong in one folder, per-user settings in another, per-user roaming settings in another, etc.
This Visual Studio Magazine article by Karl Peterson gives some VB6 code you can drop into your project to find the locations of these folders at run-time. And then this previous article by the same author gives you a nice class for using INI files, to hide away those API declarations.
This will fail as soon as anyone tries to run it on Vista or later though.
Writeable data files are not supposed to go into Program Files. Since people did it anyway Windows began enforcing the rules beginning with Vista.
Global settings belong in an application folder under CommonAppData, per-user settings go below LocalAppData, per-using roaming settings under AppData, and so on. These locations are retrieved at runtime via Shell object or API calls.
You will want to use the FileSystemObject from the Scripting runtime to combine paths and filenames properly. Although it may seem a trivial issue in reality there are corner cases that the FileSystemObject handles.
app.path would return the path of the currently executing exe,use it
Keep the EXE in the same folder

Resources