EnumPrinters() - Standarddrucker ermitteln geht nicht?!?

Für allgemeine Fragen zur Programmierung mit PureBasic.
Benutzeravatar
shadow
Beiträge: 189
Registriert: 23.03.2005 17:52
Wohnort: Lübeck

EnumPrinters() - Standarddrucker ermitteln geht nicht?!?

Beitrag von shadow »

Hallo,

folgenden Code habe ich geschrieben um alle auf dem System installierte Drucker zu ermitteln. Soweit so gut. Jetzt versuch ich aber den Standarddrucker zu ermitteln (siehe Funktion SelectDefaultPrinter()). Aber irgendwie klappt das nicht, d. h. es wird kein Drucker mit dem Attribut PRINTER_ATTRIBUTE_DEFAULT gefunden.
In der API-Doku ist das Feld Attributes der PRINTER_INFO_2-Struktur ein DWORD. Könnte es damit zusammenhängen, dass PureBasic vorzeichenlose Datentypen nicht unterstützt?
Wär nett, wenn sich das jemand anschauen könnte.

Code: Alles auswählen


EnableExplicit

Structure SPrinterInfo
	ServerName.s
	PrinterName.s
	ShareName.s
	PortName.s
	DriverName.s
	Comment.s
	Location.s
	SepFile.s
	PrintProcessor.s
	Datatype.s
	Parameters.s
	Attributes.l
	Priority.l
	DefaultPriority.l
	StartTime.l
	UntilTime.l
	Status.l
	Jobs.l
	AveragePPM.l
EndStructure

Global NewList EnumeratedPrinters.SPrinterInfo()

Procedure.s NotNull(str.l)
	Protected s$
	If str = #Null : s$ = "" : Else : s$ = PeekS(str) : EndIf
	ProcedureReturn s$
EndProcedure

; Die Flags scheinen nicht zu funktionieren ?!?
Procedure SelectDefaultPrinter()
	If CountList(EnumeratedPrinters()) = 0 : ProcedureReturn : EndIf
	ForEach EnumeratedPrinters()
		If (EnumeratedPrinters()\Attributes & #PRINTER_ATTRIBUTE_DEFAULT) <> #PRINTER_ATTRIBUTE_DEFAULT
			Break
		EndIf
	Next
EndProcedure

Procedure GeneratePrinterInfo(*p.PRINTER_INFO_2, *s.SPrinterInfo)
	*s\ServerName 			= NotNull(*p\pServerName)
	*s\PrinterName 			= NotNull(*p\pPrinterName)
	*s\ShareName 				= NotNull(*p\pShareName)
	*s\PortName 				= NotNull(*p\pPortName)
	*s\DriverName 			= NotNull(*p\pDriverName)
	*s\Location 				= NotNull(*p\pLocation)
	*s\SepFile 					= NotNull(*p\pSepFile)
	*s\PrintProcessor 	= NotNull(*p\pPrintProcessor)
	*s\Datatype 				= NotNull(*p\pDatatype)
	*s\Parameters 			= NotNull(*p\pParameters)
	*s\Attributes 			= *p\Attributes
	*s\Priority 				= *p\Priority
	*s\DefaultPriority 	= *p\DefaultPriority
	*s\StartTime 				= *p\StartTime
	*s\UntilTime 				= *p\UntilTime
	*s\Status 					= *p\Status
	*s\Jobs 						= *p\cJobs
	*s\AveragePPM 			= *p\AveragePPM
EndProcedure

Procedure AddToPrintersList(*p.PRINTER_INFO_2)
	AddElement(EnumeratedPrinters())
	GeneratePrinterInfo(*p, @EnumeratedPrinters())
EndProcedure

Procedure.l EnumPrintersEx(level.l, *name.s)
	Protected i.l, *mem.l
	Protected p.PRINTER_INFO_2
	Protected needed.l, returned.l, size.l
	
	If Not EnumPrinters_(level, *name, 2, #Null, 0, @needed, @returned)
		If needed > 0
			*mem = AllocateMemory(needed)
		Else
			ProcedureReturn GetLastError_()
		EndIf
	EndIf
	
	If Not EnumPrinters_(level, *name, 2, *mem, MemorySize(*mem), @needed, @returned)
		FreeMemory(*mem)
		ProcedureReturn GetLastError_()
	EndIf
	
	For i = 1 To returned
		CopyMemory(*mem + (i-1) * SizeOf(PRINTER_INFO_2), p, SizeOf(PRINTER_INFO_2))
		AddToPrintersList(p)
	Next
	
	FreeMemory(*mem)

	ProcedureReturn #ERROR_SUCCESS
EndProcedure

Procedure.l EnumPrinters()
	Protected ret.l = #ERROR_SUCCESS
	
	ClearList(EnumeratedPrinters())
	
	If ret <> #ERROR_SUCCESS : ProcedureReturn ret : EndIf
	ret = EnumPrintersEx(#PRINTER_ENUM_LOCAL, #Null)
	If ret <> #ERROR_SUCCESS : ProcedureReturn ret : EndIf
	ret = EnumPrintersEx(#PRINTER_ENUM_CONNECTIONS, #Null)
	If ret <> #ERROR_SUCCESS : ProcedureReturn ret : EndIf
	
	SortStructuredList(EnumeratedPrinters(), 2, OffsetOf(SPrinterInfo\PrinterName), #PB_Sort_String)
	SelectDefaultPrinter()
	
	ProcedureReturn #ERROR_SUCCESS
EndProcedure

#DEBUG_ENUM_PRINTERS = 1
CompilerIf Defined(DEBUG_ENUM_PRINTERS, #PB_Constant)
	Define def.SPrinterInfo
	EnumPrinters()
	
	Debug "Anzahl der installierten Drucker: " + Str(CountList(EnumeratedPrinters()))
	Debug ""
	Debug "Default: " + EnumeratedPrinters()\PrinterName
	Debug ""
	ForEach EnumeratedPrinters()
		Debug "ServerName: " 				+ EnumeratedPrinters()\ServerName
		Debug "PrinterName: " 			+ EnumeratedPrinters()\PrinterName
		Debug "ShareName: " 				+ EnumeratedPrinters()\ShareName
		Debug "PortName: " 					+ EnumeratedPrinters()\PortName
		Debug "DriverName: " 				+ EnumeratedPrinters()\DriverName
		Debug "Comment: " 					+ EnumeratedPrinters()\Comment
		Debug "Location: " 					+ EnumeratedPrinters()\Location
		Debug "SepFile: " 					+ EnumeratedPrinters()\SepFile
		Debug "PrintProcessor: " 		+ EnumeratedPrinters()\PrintProcessor
		Debug "Datatype: " 					+ EnumeratedPrinters()\Datatype
		Debug "Parameters: " 				+ EnumeratedPrinters()\Parameters
		Debug "Attributes: " 				+ Str(EnumeratedPrinters()\Attributes)
		Debug "Priority: " 					+ Str(EnumeratedPrinters()\Priority)
		Debug "DefaultPriority: " 	+ Str(EnumeratedPrinters()\DefaultPriority)
		Debug "StartTime: " 				+ Str(EnumeratedPrinters()\StartTime)
		Debug "UntilTime: " 				+ Str(EnumeratedPrinters()\UntilTime)
		Debug "Status: " 						+ Str(EnumeratedPrinters()\Status)
		Debug "Jobs: " 							+ Str(EnumeratedPrinters()\Jobs)
		Debug "AveragePPM: " 				+ Str(EnumeratedPrinters()\AveragePPM)
		Debug "======================================================"
	Next
	
	End
CompilerEndIf
Benutzeravatar
ts-soft
Beiträge: 22292
Registriert: 08.09.2004 00:57
Computerausstattung: Mainboard: MSI 970A-G43
CPU: AMD FX-6300 Six-Core Processor
GraKa: GeForce GTX 750 Ti, 2 GB
Memory: 16 GB DDR3-1600 - Dual Channel
Wohnort: Berlin

Beitrag von ts-soft »

>> Könnte es damit zusammenhängen, dass PureBasic vorzeichenlose Datentypen nicht unterstützt?
Nein, eigentlich nicht, ist ja nur die Art, wie der Speicher interpretiert wird,
solange der richtige Wert drinnen steht, ist das egal, solange man nicht mit
rechnen muß.

Deinen schönen Code kann ich nicht testen, erhalte sofort Zeile 82 einen
IMA, aber um den DefaultPrinter zu ermitteln, kannste den Code aus PBOSL
nehmen:

Code: Alles auswählen

Procedure.s Getdefaultprinter();retrieves the defaultprinter
   hLib=LoadLibrary_("winspool.drv")
   If hlib
      printer_n$=Space(255)
      printbuff=255
      adr=GetProcAddress_(hLib,"GetDefaultPrinterA")
      If adr
       res=CallFunctionFast(adr,@printer_n$,@printbuff)
      EndIf
     FreeLibrary_(hlib)
    EndIf
   ProcedureReturn printer_n$
EndProcedure
Debug Getdefaultprinter()
PureBasic 5.73 LTS | SpiderBasic 2.30 | Windows 10 Pro (x64) | Linux Mint 20.1 (x64)
Nutella hat nur sehr wenig Vitamine. Deswegen muss man davon relativ viel essen.
Bild
Benutzeravatar
shadow
Beiträge: 189
Registriert: 23.03.2005 17:52
Wohnort: Lübeck

Beitrag von shadow »

Hi,

der IMA von Zeile 82 ist merkwürdig. Ich habe zwei Arbeitsstationen im Einsatz. Und tatsächlich, auf der Entwicklungsplattform läuft es ohne Probleme, aber sobald ich den Code auf der anderen Maschine ausführe kriege ich auch einen IMA!?! Muss ich mal nachgehen. Danke für den Hinweis :allright:

Hmm, PBOSL möchte ich ungern verwenden, da ich die Anwendung unabhängig jeglicher Bibliotheken von Drittanbietern entwickeln möchte. Aber der Codeausschnitt funktioniert einwandfrei! Wie ist das mit der OpenSource-Lizenz in diesem Fall - ich meine, könnte ich diesen Weg übernehmen ohne gegen die Lizenz zu verstoßen?
Benutzeravatar
ts-soft
Beiträge: 22292
Registriert: 08.09.2004 00:57
Computerausstattung: Mainboard: MSI 970A-G43
CPU: AMD FX-6300 Six-Core Processor
GraKa: GeForce GTX 750 Ti, 2 GB
Memory: 16 GB DDR3-1600 - Dual Channel
Wohnort: Berlin

Beitrag von ts-soft »

shadow hat geschrieben:ch meine, könnte ich diesen Weg übernehmen ohne gegen die Lizenz zu verstoßen?
Kein Problem. Lediglich wenn Du den Source der Lib erheblich verbesserst,
so müßtest Du diese Verbesserungen wieder zugänglich machen, damit auch
die PBOSL davon profitieren kann.

Ich denke mal, die Procedure kann man zwar verbessern, deklarieren von
Variablen usw., aber nicht wesentlich :lol:
PureBasic 5.73 LTS | SpiderBasic 2.30 | Windows 10 Pro (x64) | Linux Mint 20.1 (x64)
Nutella hat nur sehr wenig Vitamine. Deswegen muss man davon relativ viel essen.
Bild
Benutzeravatar
Shardik
Beiträge: 746
Registriert: 25.01.2005 12:19

Beitrag von Shardik »

shadow hat geschrieben: Aber irgendwie klappt das nicht, d. h. es wird kein Drucker mit dem Attribut PRINTER_ATTRIBUTE_DEFAULT gefunden.
Die Konstante PRINTER_ATTRIBUTE_DEFAULT wird nur von Win9x/ME entsprechend gesetzt und der Code funktioniert daher nur unter diesen Beitriebssystemen (aber auch nur dann, wenn man in SelectDefaultPrinter() auf Gleichheit (nicht Ungleichheit!) mit #PRINTER_ATTRIBUTE_DEFAULT prüft :wink: ):
MSDN hat geschrieben: PRINTER_ATTRIBUTE_DEFAULT Windows 95/98/Me: Indicates the printer is the default printer in the system.
http://msdn2.microsoft.com/en-us/library/ms536023.aspx

Aber warum alles so kompliziert machen? gnozal hat im englischen Forum bereits 2004 ein Beispiel veröffentlicht, das sowohl alle installierten Drucker als auch den Default-Drucker unter allen Windows-Betriebssystemen ermittelt:
http://www.purebasic.fr/english/viewtop ... 83&start=2

Hier ist sein an PB 4 angepaßter Code (LinkedList als global definiert):

Code: Alles auswählen

Global NewList Imprimantes.s() 

Procedure.l GetInstalledPrinters() ; in linked list Imprimantes() 
  ClearList(Imprimantes()) 
  Buffersize.l  = 8102 
  *Buffer = GlobalAlloc_(#GMEM_FIXED | #GMEM_ZEROINIT, Buffersize) 
  TempPrinter.s = Space(1024) 
  If GetProfileString_("devices", 0, "", *Buffer, Buffersize) 
    TempString.s = PeekS(*Buffer) 
    Length.l = Len(TempString) 
    While TempString <> "" 
      GetPrivateProfileString_("devices", TempString, "", TempPrinter, 1024, "Win.Ini") 
      ; Debug "Device  : " + TempString 
      ; Debug "Driver  : " + StringField(TempPrinter,1,",") 
      ; Debug "Port    : " + StringField(TempPrinter,2,",") 
      AddElement(Imprimantes()) 
      Imprimantes() = TempString 
      TempString = PeekS(*Buffer + Length + 1) 
      Length = Length + Len(TempString) + 1 
    Wend 
  EndIf 
  GlobalFree_(*Buffer) 
  ProcedureReturn CountList(Imprimantes()) 
EndProcedure 
; 
Procedure.s GetDefaultPrinter() 
  STDPrinterName$ = Space(260) 
  If GetPrivateProfileString_("WINDOWS","DEVICE","", @STDPrinterName$, 260, "Win.Ini") 
    ImprimanteParDefaut.s = StringField(STDPrinterName$, 1,",") 
  EndIf 
  ProcedureReturn ImprimanteParDefaut 
EndProcedure

Debug "Anzahl Drucker: " + Str(GetInstalledPrinters())

ForEach Imprimantes()
  Debug Imprimantes()
Next

Debug "-----"

Debug "Default-Drucker: " + GetDefaultPrinter()
Auch der Code für den Default-Drucker ist kürzer als der von ts-soft zitierte aus der PBOSL. :wink:
Benutzeravatar
ts-soft
Beiträge: 22292
Registriert: 08.09.2004 00:57
Computerausstattung: Mainboard: MSI 970A-G43
CPU: AMD FX-6300 Six-Core Processor
GraKa: GeForce GTX 750 Ti, 2 GB
Memory: 16 GB DDR3-1600 - Dual Channel
Wohnort: Berlin

Beitrag von ts-soft »

Shardik hat geschrieben: Auch der Code für den Default-Drucker ist kürzer als der von ts-soft zitierte aus der PBOSL. :wink:
Das auslesen der nur aus kompatibilitätsgründen vorhandenen Win.ini halte
ich nicht für praktikabel. Die Werte müssen dort nicht stehen, bzw. die Ini
muß garnicht existieren :wink:

Es gibt soviele Win3.11 überbleibsel, die meist noch funktionieren, aber die
sind in meinen Augen nicht empfehlenswert. Haste den Code mal unter
Vista getestet?
PureBasic 5.73 LTS | SpiderBasic 2.30 | Windows 10 Pro (x64) | Linux Mint 20.1 (x64)
Nutella hat nur sehr wenig Vitamine. Deswegen muss man davon relativ viel essen.
Bild
Benutzeravatar
shadow
Beiträge: 189
Registriert: 23.03.2005 17:52
Wohnort: Lübeck

Beitrag von shadow »

@Shardik Hi,
...wenn man in SelectDefaultPrinter() auf Gleichheit (nicht Ungleichheit!)...
Ja, die Zeile habe ich ohne zu testen direkt im Forum auf den Ursprungszustand zurückversetzen wollen. Hab es dann da wohl übersehen :oops: Aber wie du gesagt hast, das funktioniert eh nur unter Win9x/ME.

Ich verwende die API genau aus dem Grund, den ts-soft beschrieben hat. Sollte sich jemals was am Speicherort oder -art der Einstellungen ändern, so muss ich nichts anpassen :wink:

Außerdem ist die Funktion gar nicht so umfangreich:

Code: Alles auswählen

Procedure.s EP_GetDefaultPrinter()
	Protected id.l, name$, buflen.l
	id = OpenLibrary(#PB_Any, "winspool.drv")
	If Not id : ProcedureReturn "" : EndIf
	
	name$ = Space(255)
	buflen = 255
	CallFunction(id, "GetDefaultPrinterA", @name$, @buflen)
	
	CloseLibrary(id)
	ProcedureReturn name$
EndProcedure
Aber danke für deine konstruktive Kritik Shardik :allright:
Benutzeravatar
Shardik
Beiträge: 746
Registriert: 25.01.2005 12:19

Beitrag von Shardik »

ts-soft hat geschrieben: Das auslesen der nur aus kompatibilitätsgründen vorhandenen Win.ini halte ich nicht für praktikabel. Die Werte müssen dort nicht stehen, bzw. die Ini muß garnicht existieren
Die Kompatibilität ist zumindest bis Windows XP gegeben. Microsoft selbst geht im Knowledge Base Artikel 266767 (http://support.microsoft.com/kb/266767), der am 23.8.06 zuletzt überarbeitet wurde, in einem Visual BASIC-Beispiel diesen Weg, um den Default-Drucker zu ändern:

Code: Alles auswählen

Private Sub SetDefaultPrinter(ByVal PrinterName As String, _
    ByVal DriverName As String, ByVal PrinterPort As String)
    Dim DeviceLine As String
    Dim r As Long
    Dim l As Long
    DeviceLine = PrinterName & "," & DriverName & "," & PrinterPort
    ' Store the new printer information in the [WINDOWS] section of
    ' the WIN.INI file for the DEVICE= item
    r = WriteProfileString("windows", "Device", DeviceLine)
    ' Cause all applications to reload the INI file:
    l = SendMessage(HWND_BROADCAST, WM_WININICHANGE, 0, "windows")
End Sub
Dabei wird der neue Drucker-Name in der Win.Ini eingetragen (sollte diese Datei - warum auch immer - nicht existieren, wird sie erstellt) und dann alle Anwendungen über ein SendMessage() darüber informiert. Deine obigen Argumente sind daher nicht zutreffend... :wink:
Haste den Code mal unter Vista getestet?
Ich kenne bisher keine Firma/öffentliche Verwaltung/Kunden, die auf Vista umgestiegen sind. Die am häufigsten anzutreffende Aussage im professionellen Bereich ist:
"Wir setzen Vista erst ein, wenn das erste Service-Pack erschienen ist und der Großteil der Treiber in vernünftiger Qualität für Vista verfügbar ist..."

Trotzdem wäre ich natürlich dankbar über ein Feedback von jemandem, der dies auch unter Vista getestet hat.

Mein zuerst veröffentlichtes Beispiel hat den Vorteil, von Windows 95 bis Windows XP einsetzbar zu sein (Win98, WinNT und WinXP getestet). Dein PBOSL-Beispiel läuft zum Beispiel nicht unter Windows NT... /:->
Benutzeravatar
ts-soft
Beiträge: 22292
Registriert: 08.09.2004 00:57
Computerausstattung: Mainboard: MSI 970A-G43
CPU: AMD FX-6300 Six-Core Processor
GraKa: GeForce GTX 750 Ti, 2 GB
Memory: 16 GB DDR3-1600 - Dual Channel
Wohnort: Berlin

Beitrag von ts-soft »

Dann sollte man wohl, je nach OSVersion() den passenden Code ausführen.
Die Codelänge sollte wohl keine Rolle spielen, und INI-auslesen ist sowie
eine sehr langsame API-Funktion.

Warum MS für VB programmierer solche Beispiele postet, behalte ich lieber
für mich :mrgreen:
PureBasic 5.73 LTS | SpiderBasic 2.30 | Windows 10 Pro (x64) | Linux Mint 20.1 (x64)
Nutella hat nur sehr wenig Vitamine. Deswegen muss man davon relativ viel essen.
Bild
Benutzeravatar
Shardik
Beiträge: 746
Registriert: 25.01.2005 12:19

Beitrag von Shardik »

ts-soft hat geschrieben: Dann sollte man wohl, je nach OSVersion() den passenden Code ausführen. Die Codelänge sollte wohl keine Rolle spielen, und INI-auslesen ist sowie eine sehr langsame API-Funktion.
Die Code-Länge spielt insofern eine Rolle, als daß ein kurzer übersichtlicher Code meist leichter wartbar ist (die Ausführungsgeschwindigkeit ist bei diesen Problemstellungen meist zu vernachlässigen). Aber wenn man langen, unübersichtlichen Code mit vielen Extra-Abfragen für jede mögliche Betriebssystem-Variante kodieren muß, dient dies nicht gerade der leichten Wartbarkeit und Übersicht... :wink:

Außerdem habe ich irgendwo von Microsoft gelesen (leider finde ich die Quelle nicht mehr), daß die Win.Ini zumindest bis WinXP offiziell in Form eines Mapping unterstützt wird, d.h. selbst bei Nichtvorhandensein der Win.Ini wird aus Registry-Einträgen eine "virtuelle" Win.Ini erstellt, sodaß man zumindest im Drucker-Bereich hier auf der sicheren Seite ist. Ansonsten bin ich in den meisten Fällen auch Deiner Meinung, daß man die Finger von altem (und nicht mehr offiziell unterstütztem) Win16-Kram lassen sollte... :)
Antworten