how to get the dsf code below to work? - windows

I wish to send input to multiple emulated mice using dsf(device simulation framework) incuded in the current windows ddk. My code is below the problem is device manager recogizes the device got added but my program (mouse mischief - also microsoft) doesnt create the addtional pointer like its supposed to:
WriteLine "Create first input report to send to the consumer control"
Dim strMessage
strMessage = "Press Enter key to stop looping"
WriteLine strMessage
Do While NOT WScript.StdIn.AtEndOfLine
Dim InputReport1(4)
InputReport1(0) = CByte(0)
InputReport1(1) = CByte(100)
InputReport1(2) = CByte(100)
InputReport1(3) = CByte(0)
InputReport1(4) = CByte(0)
'PromptAndWaitForEnterKey "Queue input reports for processing"
GenericHIDDev.QueueInputReport(InputReport1), 10
'PromptAndWaitForEnterKey "Wait for the device to finish enumerating. Press enter to start processing input reports."
GenericHIDDev.StartProcessing
'WriteLine "You may send additional input reports at this time..."
'PromptAndWaitForEnterKey "Press enter at any time to stop processing input reports and start cleanup."
GenericHIDDev.StopProcessing
Input = WScript.StdIn.Read(1)
Loop
Note: this is the only section i modified of the TestGenericHid sample included with the dsf in the windows ddk(device driver kit). After install of windows ddk go to c:\Program Files\dsf\GenericHid or something similar to access the vbscript file.
Need mousmischief and windows ddk to fully understand whats going on and to correctly answer this. Dont worry all samples are in vbscript and can be redone in vb.net but i dont wish to waste time converting until i get the vbscript to work.

I have since I posted this got it working with multiple mouse and within 2-3 months time will have a nice beta upload of my whole kinectmultipoint project here:
http://kinectmultipoint.codeplex.com
The code above is in a zip file at the address above in the preceding sentence.

Related

OpenEdge Progress-4GL development: how to know from an error message the procedure I'm running?

As stated in previous questions, I'm quite new at Progress-4GL development.
I've just created a windows (*.w file), together with a procedure file (*.p file), which are based on an include file (*.i file).
I've done something wrong and I get an error message, copy-paste reveals the following:
---------------------------
Fout
---------------------------
** Begin positie voor SUBSTRING, OVERLAY, enz. moet 1 of groter zijn. (82)
---------------------------
OK
---------------------------
As you can see, this is the Dutch translation of error 82:
** Starting position for SUBSTRING, OVERLAY, etc. must be 1 or greater. (82)
The SUBSTRING, OVERLAY, etc, functions require that the start position (second argument) be greater than or equal to 1.
P
I'd like to know which procedure/function is launching this error message. I'm working with the AppBuilder release 11.6 and the corresponding procedure editor, so the debugging possibilities are very limited. One thing I'm thinking of, is taking a dump of the Windows process, in order to determine the call stack, but I'm not sure how to do this. I also tried using Process Explorer and check the stack of the individual stacks of the threads inside the "procwin32.exe" process, but I'm not sure how to proceed.
By the way, I'm regularly adding message boxes to my code, which look as follows (just an example):
MESSAGE "begin procedure combobox-value-changed" VIEW-AS ALERT-BOX.
As you see, the name of the procedure is hardcoded, while in other programming languages (like C++) the procedure/function name can be shown as follows:
OUTPUT("begin procedure %s", __FUNCTION__);
Next to __FUNCTION__, C++ also knows __FILE__ (for filename) and __LINE__ (for line number).
Do such predefined values also exist in Progress 4GL, preferably release 11.6 or previous?
As ABL code is not compiled into Windows byte-code a windows debugger will not be really helpful.
You should start by adding the -debugalert startup parameter to prowin/prowin32.exe. Or add this
ASSIGN SESSION:DEBUG-ALERT = TRUE .
That will add a HELP button to all (error) messages which will open a dialog with the ABL stack trace.
As you'Re using include files, be aware that the line numbers referenced in the stack-trace are based on the debug listing, not the actual source code. So execute
COMPILE myfile.w DEBUG-LIST c:\temp\myfile.debuglist .
to receive the debug-listing file with the correct line numbers.
Are you aware of the visual debugger that's available for the AVM? https://docs.progress.com/de-DE/bundle/openedge-abl-troubleshoot-applications-117/page/Introduction.html
%DLC%\bin\proDebugger.bat
Or the Compile -> Debug menu in the AppBuilder.
It looks a bit antique, but usually does it's job.
Debugging needs to be enabled as an Administrator in proenv:
prodebugenable -enable-all
Of course the grass is greener when you switch to Progress Developer Studio as your IDE.
Regarding the second part of your question. See the PROGRAM-NAME function.
message
program-name(1) skip
program-name(2)
.
Additionally see the {} preprocessor name reference.
message 'file: {&file-name} line: {&line-number}'.

How to detect Word.Application.SaveAs timeouts?

I have a VBS script that converts a bunch of word (doc, dox, and docm) and powerpoint (pptx) files into PDFs, via Word and PowerPoint application objects and argument SaveAs.
That works fine, but for reasons unknown the processing fails for some Word files (guess: the files were created with another office version, or have some corruption). SaveAs runs indefinitely, with Word running using 100% of a CPU core, until I kill it manually.
After the SaveAs operation has ran for a programmed amount of time (say 5 minutes), I'd like to:
Abort the current SaveAs operation.
Raise an error.
I could then dispatch an error message via mail, then proceed to the next file (by specifying On Error Resume Next).
This code excerpt shows how I convert Word files to PDF in the script:
Dim oWord, Pres
Set oWord = CreateObject("Word.Application")
Set Pres = oWord.Documents.Open("/path/to/file.docm", , , msoFalse)
Pres.SaveAs "/path/to/file.pdf", 17, True 'runs forever on certain files
Pres.Close 0
How can I abort the current SaveAs operation after a specified delay has elapsed?

Is there a way to make an existing cmd window execute commands?

So here is my situtation.
I am using the Windows OS. I am running a Matlab GUI that launches another executable at startup. The other executable runs in batch mode (runs in cmd in the background).
I want to make it so when a user clicks a button on the Matlab GUI, the other executable will run a command and remain open. Is this possible?
NOTE: I do not want to open a new cmd window, I want the existing one to execute commands.
Unfortunately it does not appear that Matlab has the ability you are looking for, at least not directly. I found a post which does explain how to do it with the help of .NET though, which is fortunate since you are on the Windows platform: http://www.mathworks.com/matlabcentral/answers/72356-using-matlab-to-send-strings-to-the-stdin-of-another-console-application
I have copied a lot of this from that post
function lh = task()
% Initialize the process and its StartInfo properties.
% The sort command is a console application that
% reads and sorts text input.
process = System.Diagnostics.Process;
process.StartInfo.FileName = 'sort.exe';
process.EnableRaisingEvents = true;
process.StartInfo.CreateNoWindow = true;
% Set UseShellExecute to false for redirection.
process.StartInfo.UseShellExecute = false;
%Redirect the standard output of the sort command.
process.StartInfo.RedirectStandardOutput = true;
% Set our event handler to asynchronously read the sort output.
lh = process.addlistener('OutputDataReceived',#sortOutputHandler);
% Redirect standard input as well. This stream
% is used synchronously.
process.StartInfo.RedirectStandardInput =true;
% Start the process.
process.Start();
%Use a stream writer to synchronously write the sort input.
ProcessStreamWriter = process.StandardInput;
% Start the asynchronous read of the sort output stream.
process.BeginOutputReadLine();
%Prompt the user for 4 input text lines. Write each
%line to the redirected input stream of the sort command.
numInputLines = 0;
while(numInputLines ~= 4)
inputText = input('Enter a text line (or press the Enter key to stop):', 's');
numInputLines = numInputLines + 1;
if(~isempty(inputText))
ProcessStreamWriter.WriteLine(inputText);
end
end
disp('end of input stream');
%end the inputr stream to the sort command
ProcessStreamWriter.Close();
% wait for the sort process to write the sorted text lines
process.WaitForExit();
process.Close();
end
For handling any output from the CMD you need:
function processOutputHandler(obj,event)
%collect the sort command output and print in command window
if(~isempty(event.Data))
disp(event.Data);
end
end
You can use a stream writer to synchronously write the sort input.
processStreamWriter = process.StandardInput;
Again, I have taken this from the previously mentioned post so I can't take any credit for the code, but I do think it will be able to accomplish what you are looking for. Unfortunately, I am pretty sure this will accomplish what you need. I don't have Matlab on a Windows platform at the moment or I would test this. If you need information on using .NET code in MATLAB (its not immediately clear if you need to add some stuff to establish the .NET interface) MathWorks provides some documentation on it: http://www.mathworks.com/help/matlab/matlab_external/using-net-from-matlab-an-overview.html
Hopefully this helps, or gets you started. Let me know if there's anything else I missed.
You can approach this from the ansys side. Start it with -B-R to read a python script.
From there, you can establish some two-way protocol, for example polling files or, better, by running a web server from python.
Then you can communicate from matlab with that running instance of ansys. If you opt for a web server, you use MATLABs urlread().
Setting up a web-server with python is easy, but you have to learn how to dispatch commands to the hosting ansys application.

USB_MASS_STORAGE driver crashes after vb script modify USBSTOR key

I have this script written by myself.
Basically what I wanted was script that "unlock" the front usb only for specific device.
The situation is such that we have a workstation with registry key USBSTOR\Start set to 4(disable) so the front usb is not available without some additional work of our IT department - this way we control who can access the usb
But the employees must use a camera to take pictures for specific needs and to send them through email clients.So we want to automate the "lock/unlock" phase.
The usb is unlocked when the device of interest is inserted,it stays "unlocked" while the device is in usb and after the device is plugged out,the script "lock" the usb again.
I have decided to use .vbs.The script works as I expected,but after the "lock" phase,the USB_MASS_STORAGE driver get crashed.I must uninstall it and restart the Windows for the driver to be reloaded again and to work properly.After I have run the script several times,the registry value in USBSTOR\Start does not affect the usb,i.e the usb is unlocked even if there is 4.If I change the value from 4 to 3 the driver crashes.
I am looking for some advices.
Here is the code for usbstor.vbs script. I have used a lot of comments,some of them explain a pretty obvious things,but I have decide so.
' Script for access to Front Usb (a.k.a USB MASS STORAGE)
' The usb is locked by default(the value in Registry Key USBSTOR/Start is 4 - disable).It is enabled(the value in Registry Key USBSTOR/Start is 3 - enable) when the device of interest is put into front usb.
' The usb is in "enable" state ,while the device is into it. After it is removed,the Registry Key USBSTOR/Start value is set to 4(disable).
' The device is recognized by hardware id ,which is known in advance by searching USBSTOR,when the device is inserted. This script is for pc,where what we want is access to front usb only for spcecific device(a camera in our case).
' For everything else the usb should be disabled.The script is loaded in RAM and if the while loop condition isn't change to false,we must kill the process within TaskManager
' The CPU time is high > 98 while the script runs.I came to this solution for my problem,but any ideas for improvements or for different logic are highly welcomed.
Option Explicit On
Dim Shell,Start,Hwid,Enum_0,Enum_1,Count,Flag_0,Flag_1,Check_0,Check_1 'Dimension of varables we are going to use in the script.
Set Shell = CreateObject("WScript.Shell") 'Create an object to work with Windows Registry.
'Start = Shell.RegRead("HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\USBSTOR\Start") 'Save the value of the registry key into Start variable
Hwid = "USB\Vid_0930&Pid_6545\001D92A866B8B8B1934703FE" 'hardawre id of device of interest.We get it from the registry and the script scan for this id.It is constant string
Count = 1 'Initialize the Count variable with value of 1.We use it as a condition in endless while() loop.It makes script run in real-time,so it can scan uninterupted for changes in the registry
QueryEnum0 ' The subroutines QueryEnum0 and QueryEnum1.The id is either in USBSTOR\Enum\0 or in USBSTOR\Enum\1 .That is for sure.
QueryEnum1 ' Declaration before definition - not exactly explanation.
'The purpose of these two subroutines is: create an object everytime the sub is called ,thus read the value in Enum\0 or in Enum\1 constantly as "scanning"
'Probably not so elegant solution to somebody,but actually it works.
Sub QueryEnum0 ' Enter the sub
Dim Flag_Enum_0,Shell ' Declare local variables.They will be created each time the sub is invoked.
Set Shell = CreateObject("WScript.Shell") 'Create an object to work wirh registry any time the sub is called
On Error Resume Next 'Error handling
Flag_Enum_0 = Shell.RegRead("HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\USBSTOR\Enum\0") 'Try to read reg value into Flag_Enum_0. The purpose
On Error GoTo 0
Flag_0 = Flag_Enum_0 'Assign the value to variable Flag_0,outside of sub.The memory for Flag_0 is set once and lasts while the script runs.
End Sub
' Same as QueryEnum0
Sub QueryEnum1
Dim Flag_Enum_1,Shell
Set Shell = CreateObject("WScript.Shell")
On Error Resume Next
Flag_Enum_1 = Shell.RegRead("HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\USBSTOR\Enum\1")
On Error GoTo 0
Flag_1 = Flag_Enum_1
End Sub
Do While Count = 1 'Real-time loop,the code within while is running while count is equal to 1. The script is loaded in memory constanlty.
On Error Resume Next
Enum_0 = Shell.RegRead("HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\USBSTOR\Enum\0") ' Try to read hardware id if it is in Enum\0
On Error GoTo 0 '
On Error Resume Next
Enum_1 = Shell.RegRead("HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\USBSTOR\Enum\1") 'Try to read hardware id if it is in Enum\1
On Error GoTo 0
If StrComp(Hwid,Enum_0) <> 0 And StrComp(Hwid,Enum_1) <> 0 Then 'Check if both reg keys are empty
MsgBox "There is no device in the front usb.Please put the device or see the connection"
ElseIf StrComp(Hwid,Enum_0) = 0 Then 'If the hardware id is in Enum\0,thus assigned to Enum_0
Shell.RegWrite "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\USBSTOR\Start",3 'Enable the usb by "unlock" it
On Error Resume Next
QueryEnum0 'Invoke sub QueryEnum0.If the id we looking for is in Enum\0,we know that it is assigned to Flag_0 also
Check_0 = Flag_0 'Use another variable to copy value from Flag_0.
On Error GoTo 0
If StrComp(Hwid,Check_0) = 0 Then 'Compare the constant Hwid with the value in Check_0,test for id
Msgbox "Check_0 still holds the hardware id" 'Some messages to inform us whats happening
else
MsgBox "Check_0 does not contain the hardware id anymore"
Shell.RegWrite "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\USBSTOR\Start",4 'Disable the front usb
Count = 2 'End the while loop,count is 2,so the condition is false .The loop breaks.
End If
ElseIf StrComp(Hwid,Enum_1) = 0 Then 'If the hardware is in Enum\1....same as above mentioned
Shell.RegWrite "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\USBSTOR\Start",3
On Error Resume Next
QueryEnum1
Check_1 = Flag_1
On Error GoTo 0
If StrComp(Hwid,Check_1) = 0 Then
MsgBox "Check_1 still holds the hardware id"
MsgBox Check_1
else
MsgBox "Check_0 does not contain the hardware id anymore"
Shell.RegWrite "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\USBSTOR\Start",4
Count = 2
End If
End If
Loop
' Useful information for me
'HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\USBSTOR -> value name -> Start ,value data = 3(enable) = 4(disable)
'HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\USBSTOR\Enum -> value name -> 1 or 0 ,value data we look for is -> USB\Vid_04da&Pid_2372\5&2f621ee5&0&8
' USB\Vid_04da&Pid_2372\5&2f621ee5&0&8 - camera id in our case
' fantom value - USB\Vid_03f0&Pid_032a\000000000Q912WFBSI1c - name: 0 ,type: REG_SZ,in the key Enum.This is another hardware id,which is strange somehow,because I do not have any device
' inserted in my usb.However,I take this value into account,thus use both keys 0 and 1 within Enum to scan for the id I need.
According to the Microsoft documentation the data in the CurrentControlSet Registry tree is used during start up and driver initialization.
Also with Windows 7 and later any changes to HKEY_LOCAL_MACHINE must be made by a utility running as an Administrator. Otherwise the changes will be made to a user clone of HKEY_LOCAL_MACHINE and changes will affect only the user under whose account the utility was run.
See HKLM\SYSTEM\CurrentControlSet\Services Registry Tree which states
"A driver can store global driver-defined data under its key in the
Services tree. Information that is stored under this key is available
to the driver during its initialization."
Everything that I have found thus far concerning additional security for USB storage devices by setting this value indicates that it is done once as a preventive measure. So it would appear that the approach you outlined is not a feasible solution.
There may also be an initial state issue in that the until a USB mass storage device is plugged in, the device driver is not fully initialized hence this data may not have yet been accessed. Readings also seem to imply that it will depend on whether the device has been previously plugged in successfully, creating the necessary Registry data and driver initialization or not.
I think it is pretty safe to say that changing the Registry value on the fly in this way was not design intent for Windows USB drivers.
See also this page of Microsoft Knowledge Base article 103000, CurrentControlSet\Services Subkey Entries for details about the data in this Registry entry. This article says the following about the Start keyword values.
0x3 (Load on demand) Available, regardless of type, but will not be started until the user starts it (for example, by using the Devices icon in Control Panel).
0x4 (disabled) NOT TO BE STARTED UNDER ANY CONDITIONS.
See also the following stackoverflow posts.
C# Disable/Enable USB ports
Enable and Disable USB port
Win32 API function to programmatically enable/disable device

Windows: How to tell printer to issue a FormFeed during printing?

i need to tell a printer driver to issue a form feed.
i'm printing directly to a printer using the:
OpenPrinter
StartDocPrinter
StartPagePrinter
WritePrinter
EndPagePrinter
EndDocPrinter
ClosePrinter
set of API calls.
A lot of the inspiration came from KB138594 - HOWTO: Send Raw Data to a Printer by Using the Win32 API. An important point to note in that KB article is that they (and my copied code) start the document in RAW mode:
// Fill in the structure with info about this "document."
docInfo.pDocName = "My Document";
docInfo.pOutputFile = NULL;
docInfo.pDatatype = "RAW";
StartDocPrinter(hPrinter, 1, docInfo);
Note: RAW mode (as opposed to TEXT mode) means we are issuing raw bytes to the printer driver. We promise to talk in the language it understands.
We can then use WritePrinter to write everything we want:
WritePrinter(hPrinter, "Hello, world!"); //note, extra parameters removed for clarity
WritePrinter(hPrinter, 0x0c); //form-feed
The problem here is the 0x0c form-feed character. Because we've opened the printer in RAW mode, we are promising we will send the printer driver bytes it can process. The drivers of most printers take 0x0C to mean you want to issue a form-feed.
The problem is that other printers (PDF printer, Microsoft XPS Printers) expect RAW print jobs to be in their own printer language. If you use the above to print to an XPS or PDF printer: nothing happens (i.e. no save dialog, nothing printed).
i asked for a solution to this question a while ago, and a response was that you have to change the document mode from RAW:
docInfo.pDatatype = "RAW";
to TEXT:
docInfo.pDataType = "TEXT";
Well this probably is because you send
"RAW" data directly to the printer,
and RAW can be any PDL. But the XPS
driver will probably only understands
XPS, and it will probably just ignore
your "unknown: Hello, world!0xFF" PDL. The
XPS driver will probably, if any, only
accept XPS data when you write
directly to it.
If you want to render text on the XPS
driver, you should use GDI. You might
be able to send plain text to the
driver if you specify "TEXT" as the
datatype. The print processor attached
to the driver will then "convert" the
plaintext for you by rendering the job
via GDI to the driver.
So that worked, i changed my code to declare the print document as TEXT:
// Fill in the structure with info about this "document."
docInfo.pDocName = "My Document";
docInfo.pOutputFile = NULL;
docInfo.pDatatype = "TEXT";
StartDocPrinter(hPrinter, 1, docInfo);
WritePrinter(hPrinter, "Hello, world!");
WritePrinter(hPrinter, 0x0c); //form-feed
And then the Save As dialog for XPS and PDF printers appear, and it saves correctly. And i thought all was fixed.
Except months later, when i tried to print to a <quote>real</quote> printer: the form-feed doesn't happen - presumably because i am no longer printing in "raw printer commands" mode.
So what i need is the Windows-ish way of issuing a form feed. i need the API call that will tell printer driver that i want the printer to perform a form-feed.
My question: How to tell a printer to issue a Form-Feed during printing?
Background on Data Types
The print processor tells the spooler to alter a job according to the document data type. It works in conjunction with the printer driver to send the spooled print jobs from the hard drive to the printer.
Software vendors occasionally develop their own print processors to support custom data types. Normally, the print processor does not require any settings or intervention from administrators.
Data types
The Windows printing process normally supports five data types. The two most commonly used data types, enhanced metafile (EMF) and ready to print (RAW), affect performance in different ways on both the client computer and the print server computer.
RAW is the default data type for clients other than Windows-based programs. The RAW data type tells the spooler not to alter the print job at all prior to printing. With this data type, the entire process of preparing the print job is done on the client computer.
EMF, or enhanced metafile, is the default datatype with most Windows-based programs. With EMF, the printed document is altered into a metafile format that is more portable than RAW files and usually can be printed on any printer. EMF files tend to be smaller than RAW files that contain the same print job. Regarding performance, only the first portion of a print job is altered, or rendered on the client computer, but most of the impact is on the print server computer, which also helps the application on the client computer to return control to the user faster.
The following table (taken from MSDN) shows the five different data types supported by the default Windows print processor:
Data type: RAW
Directions to spooler: Print the document with no changes.
Use: This is the data type for all clients not based on Windows.
Data type: RAW [FF appended]
Directions to spooler: Append a form-feed character (0x0C), but make no other changes. (A PCL printer omits the document's last page if there is no trailing form-feed.)
Use: Required for some applications. Windows does not assign it, but it can be set as the default in the Print Processor dialog box.
Data type: RAW [FF auto]
Directions to spooler: Check for a trailing form-feed and add one if it is not already there, but make no other changes.
Use: Required for some applications. Windows does not assign it, but it can be set as the default in the Print Processor dialog box.
Data type: NT EMF 1.00x
Directions to spooler: Treat the document as an enhanced metafile (EMF) rather than the RAW data that the printer driver puts out.
Use: EMF documents are created by Windows.
Data type: TEXT
Directions to spooler: Treat the entire job as ANSI text and add print specifications using the print device's factory defaults.
Use: This is useful when the print job is simple text and the target print device cannot interpret simple text.
You can see the print processors available for a printer, and the data types that each processor supports, through the properties of a printer in the control panel:
See also
Send ESC commands to a printer in C#
Feed paper on POS Printer C#
Print raw data to a thermal-printer using .NET
Yeah, that doesn't work. You are intentionally bypassing the printer driver, the chunk of code that presents a universal interface to any printer. Which leaves you to deal with the peculiarities of each specific printer model.
There are some common interfaces, the one you used in your code is the one that dot matrix printers of old used. PCL is common on Hewlett Packard laser printers. Postscript is common on high-end printers. The latter two have their own incantations to get a form feed.
Then there's the ocean of cheap laser and ink jet printers. They often don't have a well defined interface at all. Instead of having a processor inside the printer that translates printer commands to dots on paper, they let the printer driver do all the hard work. You'll never get one of those going, the interface is proprietary and undocumented.
The printer driver is your friend here. PrintDocument the class to use it. Getting a form feed is easy, just set e.HasMorePages = true and exit the PrintPage event handler. You already saw the StreamPrinter class I linked.
I'm unfamiliar with the TEXT document type, but I presume it's just a lowest common denominator "dumb printer" representation. If so, it might recognize a form-feed character, except you've been using the wrong character - it's not 0x12 or 0xFF, it's 0x0c. See http://en.wikipedia.org/wiki/Ascii
Since my last answer was no help, lets try the obvious. Have you tried doing EndPagePrinter followed by StartPagePrinter whenever you need a page break?
If that still doesn't work you may need to do it the hard way, using GDI. The stack looks just slightly different from the one you're using:
CreateDC
CreateFont
SelectObject
StartDoc
StartPage
TextOut
EndPage
EndDoc
DeleteDC
You'll be required to manage a font and place the text on the page yourself at each line position.

Resources