How to "flatten" a PDF via "Microsoft Print To PDF" using Win32 API calls in VBA? - vba

I am trying to have my Excel VBA code read an interactive PDF (i.e. a PDF with dropdowns, calendars...etc.). I normally read PDF files by opening them in Word; however, Word removes all the interactive controls while rendering the file (e.g. a calendar control gets completely removed...along with its value).
I know that I can "flatten" the PDF by opening it in Adobe Acrobat Reader (or any browser) and printing it to a PDF (thus replacing all the controls with their selected values). The question is how do I do this programmatically in VBA. I know there are thousands of questions on S.O. about this topic; but, all of the answers either require installing Adobe Acrobat Pro, using a third-party installed application (e.g. CutePDF), submitting the file to an online API, or most commonly, using SendKeys or Win32 API calls to interact with the "Save As" dialog box that appears. The code I am writing will be distributed to multiple users; so, any additional application installations is out of the question (both Acrobat Pro and other third-party conversion tools). The files contain proprietary data; so, an online API is out as well. As far as SendKeys or SendMessage is concerned...frankly, there's got to be a better way.
I've tried numerous methods including Chrome's headless "print-to-pdf"; but it appears that only accepts HTML files (I even tried embedding the PDF into an HTML file but it still did not work).
The closest I've gotten is by using the below code to send data directly to the "Microsoft Print to PDF" driver (it's based on the process described here). The code successfully creates a PDF file...but it's a zero byte file. It seems that the data read from the input file is not being properly accepted by the "WritePrinter" function (although no error occurs). Although the documentation of the "WritePrinter" function states that the "pBuf" parameter should be a byte array, the person who asked this question was passing a string and getting it to work (understanding that they were not trying to print to a GDI printer). My code still just produces a blank PDF file if I convert the byte array to a string or even just read the contents of the input file using FSO's "ReadAll" method.
It is also worth nothing that the return value of the "WritePrinter" function is "1" and the value of the "pcWritten" output is the correct number of bytes...it's just that the PDF file that is produced has a file size of 0 bytes.
So, can anyone figure out how to get the "WritePrinter" function to accept the data being read from the input file?
By the way, an enormous gold star to anyone who can figure this out because, based on my research, the internet is begging for a way to do this without using Acrobat Pro or having to interact with the "Save As" dialog!
UPDATE#1: I have found a few posts online where users are experiencing this issue of the "Microsoft Print to PDF" driver generating blank files manually (here and here). Apparently the primary culprit is special characters in the input file name. I wanted to make it clear that I do not believe this is the issue I am having, as I can print to PDF perfectly fine manually...just not via the code in this post. (Also, just so it's been said, neither the input file path/name nor output file path/name contains special characters). Also, I don't believe it's an issue with the specific install of the print driver (as suggested in some posts) as my code creates 0 byte files on 3 different computers with 3 different OS builds of Windows (18363.1082, 18363.1139, and 19041.572)
UPDATE#2: In my continued research of this issue I found this post on MSDN's Visual Studio help forums. I understand it's for C# but one of the contributers states:
So you should convert managed byte array into unmanaged array, then invoke the method
He provides C# code that uses the "Marshal.AllocCoTaskMem" and "Marshal.Copy" functions to "Copy the managed byte array into the unmanaged array". I'm not familiar with the terms "managed" or "unmanaged" byte arrays so I will continue to do some research on those.
Does anyone have any experience with the "Marshal.AllocCoTaskMem" and "Marshal.Copy" functions from VBA within Excel (VB6)?
UPDATE#3: It has been brought to my attention that the code I've written will ONLY print XPS files to a PDF. I've converted my original interactive PDF to an XPS manually and confirmed that my code worked perfectly in writing that XPS to a PDF.
This now leaves me back at square one: how do I programmatically read an interactive PDF without utilizing any third-party applications or online converters?
Anyone have any ideas?
Type DOCINFO
lpszDocName As String
lpszOutput As String
lpszDatatype As String
End Type
Private Declare PtrSafe Function OpenPrinter Lib "winspool.drv" Alias "OpenPrinterA" (ByVal pPrinterName As String, phPrinter As Long, ByVal pDefault As Long) As Long
Private Declare PtrSafe Function StartDocPrinter Lib "winspool.drv" Alias "StartDocPrinterA" (ByVal hPrinter As Long, ByVal Level As Long, pDocInfo As DOCINFO) As Long
Private Declare PtrSafe Function StartPagePrinter Lib "winspool.drv" (ByVal hPrinter As Long) As Long
Private Declare PtrSafe Function WritePrinter Lib "winspool.drv" (ByVal hPrinter As Long, pBuf As Any, ByVal cdBuf As Long, pcWritten As Long) As Long
Private Declare PtrSafe Function EndPagePrinter Lib "winspool.drv" (ByVal hPrinter As Long) As Long
Private Declare PtrSafe Function EndDocPrinter Lib "winspool.drv" (ByVal hPrinter As Long) As Long
Private Declare PtrSafe Function ClosePrinter Lib "winspool.drv" (ByVal hPrinter As Long) As Long
Public Sub Print_File_To_PDF(strInputFile As String, strOutputPDF As String)
Dim udtDocInfo As DOCINFO
Dim lngPrinterCheck As Long, lngFileNumber As Long, lngPrinterHandle As Long, lngPrinterOutput as Long
Dim arrBytes() As Byte
'Get handle of printer
lngPrinterCheck = OpenPrinter("Microsoft Print To PDF", lngPrinterHandle, 0)
If lngPrinterCheck = 0 Then
Exit Sub
End If
'Define document info
With udtDocInfo
.lpszDocName = CreateObject("Scripting.FileSystemObject").GetBaseName(strOutputPDF)
.lpszOutput = strOutputPDF
.lpszDatatype = "RAW"
End With
'Read file into byte array
lngFileNumber = FreeFile
Open strInputFile For Binary Access Read As lngFileNumber
ReDim arrBytes(LOF(lngFileNumber))
Get lngFileNumber, , arrBytes()
Close lngFileNumber
'Print byte array to PDF file
Call StartDocPrinter(lngPrinterHandle, 1, udtDocInfo)
Call StartPagePrinter(lngPrinterHandle)
Call WritePrinter(lngPrinterHandle, arrBytes(1), UBound(arrBytes), lngPrinterOutput)
Call EndPagePrinter(lngPrinterHandle)
Call EndDocPrinter(lngPrinterHandle)
Call ClosePrinter(lngPrinterHandle)
End Sub

Related

Creating a Program for a function generator

so i just bought a function generator and i want to connect the ports from my program. The folder of the driver came with a program in vb6, so that makes me dont need to create the full program.
Even if im not a pro in visual basic, i translated almost everything into vb.net, but when i run the program it gives me the following error when runs Writefile:
System.AccessViolationException
i have searched in lot of websites and forums and tried almost everything but i dont know how to fix it. when every data arrives to "writefile" is like this:
WriteFile(hComm, bytebuf_t(0), wLen, gLen, 0&)
hcomm: a large number, always something like 42221250801565695
bytebuf_t(0): 115
wlen: 14
glen: 0
0&: (i dont really get this).
also, the writefile command is this.
Public Declare Function WriteFile Lib "kernel32" (ByVal hCommDev As Long, lpBuffer As Byte,
ByVal nNumberOfBytesToWrite As Long, lpNumberOfBytesWritten As Long, lpOverlapped As Long) As Long
i also tried to use system.io.filestream but didn't work either

Experiencing problems with copy/paste excel objects/shapes with VBA

I have some complex code which takes some user inputs (names of shapes to copy) then copies said shapes from one sheet to another multiple times. The items are grouped shapes drawn in Excel and all named correctly and uniquely.
I receive copy and paste errors intermittently "Method 'Paste' of object _Worksheet' failed" and "Method 'copy' Of Object '_worksheet' Failed. Through researching the problem we understand that it is fairly common and has something to do with programmes which conflict with Excel when they are accessing the clipboard.
So far, my colleague and I have deduced that 2 programmes in particular interfere the most with the copy/paste operations - Adobe Reader and Autodesk Powershape. (Autodesk and Adobe both have Reference Libraries available within VBA, not sure if this is a coincidence?)
The problem used to occur very frequently whilst we had the programmes open, so we wrote the following macros/functions to try and stablise the code:-
Public Sub CopyShape(ItemName, CopyDestination)
Call ClearClipboard Sheets(CopyDestination).Shapes(ItemName).Copy
Do Until IsClipboardEmpty = False DoEvents Loop
End Sub
Where "ClearClipboard" is:-
Public Function ClearClipboard()
OpenClipboard (0&)
EmptyClipboard
CloseClipboard
End Function
and the function IsClipboardEmpty is:-
Function IsClipboardEmpty() As Boolean
IsClipboardEmpty = (CountClipboardFormats() = 0)
End Function
with the following public declarations:-
Declare Function OpenClipboard Lib "user32" (ByVal hwnd As Long) As Long
Declare Function EmptyClipboard Lib "user32" () As Long
Declare Function CloseClipboard Lib "user32" () As Long
Public Declare Function CountClipboardFormats Lib "user32" () As Long
This code works quite a lot of the time (far better than trying to use "DoEvents" after the copy operation which just failed miserably) as it forces the code to check if the copied item is in the clipboard before trying to paste it, but it doesn't always work - something in the background still messes up the code.
Is there any way of either:-
locking and unlocking the clipboard using VBA or APIs?
using a completely different method of copying and pasting the shapes?
Any and all solutions welcome and of course happy to answers any questions.
Thanks

Issue in calling VB6 native dll from VB.net Project

I have a vb6 project in which I use a dll library to do some tasks. For that, I add a module which declares all functions of that library such as:
Declare Function myFunction Lib "abcdapi.dll" (ByVal hVar1 As Long, ByVal hVar2 As Long) As Long
When I call this function (and many other similar) I'm able to do the work and correct Long pointer is returned. But when I try to do the same thing by VB.net (by importing the same library using dllimport and calling the same function), I get no result. although it does return some pointer but that doesn't produce correct result.
Private Const ABCD As String = "abcdapi.dll"
<DllImport(ABCD)>
Public Shared Function myFunction(ByVal hVar1 As IntPtr, ByVal hVar2 As IntPtr) As IntPtr
End Function
When I try to call the above function, it doesn't work. I even tried creating a dll in my vb6 project using the function and try to use imports keyword to call that new dll but that doesn't work either. What could be the issue and how do I make it work.
The docos you referenced show:
TM1IMPORT TM1_BOOL TM1API TM1ValBoolGet(TM1U hUser, TM1V vBool );
Is it possible that TM1U and TM1V are defined as 32 bit data types in that API and you are running your .NET code on a 64 bit machine, making your IntPtr a 64 bit data type? (If the API came with C header files you can see how those data types are defined). Try recompiling your .NET code to "x86" and try it again.
I just copied this code from your comment above:
the function call is below:
ibOK = TM1ValBoolGet(hUser, voTemp)
In VB.net: <<< I assume here you meant VB6
Declare Function TM1ValBoolGet Lib "tm1api.dll" (ByVal hUser As Long, ByVal vBool As Long) As Integer
In vb.net:
<DllImport(TM1APIDLL)> Public Shared Function TM1ValBoolGet(ByVal hUser As IntPtr, ByVal vBool As IntPtr) As Integer
End Function
It is probably a typo, but that return type in your VB6 is not the same as the one in VB.NET. A VB6 Integer is 16 bits and an Integer in VB.NET is 32 bits.

RFID Programming With VB.NET

I'm trying to use RFID Contactless Smart Card (T8/D8 Series) for my final project.
But i have problem acessing the dcrf32.dll file in my form.
There are 3 things included in RFID Package, (therefore : 12 smartcards, an usb port, and a driver CD)
In the driver CD, there are so many sample project in some programming language, except VB.NET, i tried them one by one, and that's work.
There is a folder named "win32-dll", it have 3 files inside (dcrf32.dll, dcrf32.h, dcrf32.lib). I have to copy them all in every sample program that i want to use.
They also have example in VB.6, when i try to run it, no problems at all. It works.
(i also put those 3 dcrf files in the %windows%\System32 folder).
But, when i try to build my own project with Visual Studio 2010 and using VB.NET programming language, i have a problem calling the dcrf32.dll file.
I migrate the VB6 source code to VB.NET, it has error when giving parameters to dc_init function.
Can anybody help me? Where is my fault?
Here are the links that important to solve my problems :
Here
I just need help with my first button, and you don't have to help me with the other button like in VB 6 example program.
I just wanna know how to connect the dcrf32 files, and why in VB.NET it has error but in VB 6 it works properly.
I wonder it.
The problem that you are having is dealing with your Declare Functions and the fact there are changes to the size of integers and longs etc. in VB.Net. Look at this Link and this Code Project Article on using PInvoke.
i.e. An Integer in VB6 is a Short in VB.Net, A Long in VB6 is an Integer in VB.Net. The % in your Declares is a VB6 Integer and will become a short.
Declare Auto Function dc_init Lib "dcrf32.dll" (ByVal port as Short, ByVal baud As Integer) As Integer
Declare Auto Function dc_exit Lib "dcrf32.dll" (ByVal icdev As Integer) As Short
Declare Auto Function dc_request Lib "dcrf32.dll" (ByVal icdev As Integer, ByVal mode as Short, tagtype As Integer) As Short
Declare Auto Function dc_anticoll Lib "dcrf32.dll" (ByVal icdev As Integer, ByVal bcnt as Short, snr As Integer) As Short
...

Error when reading registry with Visual Basic 6 running on Win7

I have inherited a VB6 application from a friend of a family member, who wants to have some enhancements done to it.
I haven’t developed in VB for more than 3 years (I’m currently developing in MS Dynamics Ax).
I’ve recently upgraded my hardware and am now running Win7. The last time I worked with the app (about a year and a half ago) was on a WinXP platform, and everything worked fine. Now when I run the app (through code) on Win7, I get an error when trying to read from the registry.
Yes, I am running VB as administrator.
The code to read from the registry is:
Public Function sReadRegistry(ByVal hKeyRoot As Long, _
ByVal sSubKey As String, _
ByVal sValueName As String) As String
Dim r As Long
Dim sData As String * 255
Dim lDataSize As Long
Dim sTempVal As String
Dim readValue As String
lDataSize = 255
'Get the Value Requested
lDataSize = 255
r = VRegReadString(hKeyRoot, sSubKey, sValueName, sData, lDataSize)
If r Then
sTempVal = ""
Else
sTempVal = Left$(sData, lDataSize - 1)
End If
sReadRegistry = sTempVal
End Function
The “VRegReadString “ is declared within a module; and is declared as follows:
Declare Function VRegReadString Lib "VREG" (ByVal hKeyRoot As Long, ByVal sSubKey As String, ByVal sValueName As String, ByVal sData As String, ByRef lDataSize As Long) As Long
It complains about the “VREG” library…
The error I get is: “File not found: VREG”.
Is there a reference or component that I forgot to select? Can somebody please help with a solution?
Thanks in advance.
Seeing that the function declaration is an import from an external library called "VREG", you are probably missing the actual library itself, i.e. VREG.DLL. Unfortunately, this doesn't seem to be a common library, so you'd have to come up with it yourself.
Good news is, though, accessing the registry is not really hard and can be done with just the bare Windows API, especially seeing that VREG.DLL does not really seem to add a good deal of abstraction to the regular API. Take a look at these functions:
Registry Functions
...which you can use to easily re-write registry access, provided you fail to procure the needed DLL from somewhere.