Print labels via a redirected printer - winapi

My task is to print labels via a thermal printer. To this purpose a string of tspl(programming language that the printer understands) commands is generated and sent to the printer. The latter is done in C# with the following method taken from the microsoft example "How to send raw data to a printer by using Visual C# .NET"
public static bool SendBytesToPrinter(string szPrinterName, IntPtr pBytes, Int32 dwCount)
{
Int32 dwError = 0, dwWritten = 0;
IntPtr hPrinter = new IntPtr(0);
DOCINFOA di = new DOCINFOA();
bool bSuccess = false; // Assume failure unless you specifically succeed.
di.pDocName = "My C#.NET RAW Document";
di.pDataType = "RAW";
// Open the printer.
if (OpenPrinter(szPrinterName.Normalize(), out hPrinter, IntPtr.Zero))
{
// Start a document.
if (StartDocPrinter(hPrinter, 1, di))
{
// Start a page.
if (StartPagePrinter(hPrinter))
{
// Write your bytes.
bSuccess = WritePrinter(hPrinter, pBytes, dwCount, out dwWritten);
EndPagePrinter(hPrinter);
}
EndDocPrinter(hPrinter);
}
ClosePrinter(hPrinter);
}
// If you did not succeed, GetLastError may give more information
// about why not.
if (bSuccess == false)
{
dwError = Marshal.GetLastWin32Error();
MessageBox.Show("Error " + dwError);
}
return bSuccess;
}
The actual printer is selected in a PrintDialog
private void button2_Click(object sender, EventArgs e)
{
string s = "CLS\nPRINT 1\n"; // device-dependent string, need a FormFeed?
// Allow the user to select a printer.
PrintDialog pd = new PrintDialog();
pd.PrinterSettings = new PrinterSettings();
if (DialogResult.OK == pd.ShowDialog(this))
{
// Send a printer-specific to the printer.
RawPrinterHelper.SendStringToPrinter(pd.PrinterSettings.PrinterName, s);
}
}
In my working environment everything works fine. The environment is a LAN consisting of 3 computers and 1 shared thermal printer:
PC_with_printer - it runs on Windows 7 and has the printer connected to it via USB
PC_aware - it runs on Win 8 and knows about the shared printer
PC_unaware - it runs on WinXP and doens't have the shared printer installed
From PC_aware I connect to PC_unaware via rdp and the printer appears in the list of printers as redirected. Then I run my application , choose the redirected printer and get my empty label printed. So far so good.
Problems begin when I replace PC_unaware with another computer. It runs on Windows Server 2008 R2 and is not in our LAN. Let's call it SERVER. So I carry out the same experiment:
From PC_aware I connect to SERVER via rdp using its public ip address
My thermal printer appears in "Printers and Devices" as "TSC_TDP-244 (redirected 20)"
I go to the printer's properties and click Print Test Page, and it gets printed
I run my app and the printer doesn't print anything.
I have checked return values of all winapi functions that are used in SendBytesToPrinter method (OpenPrinter,StartDocPrinter, StartPagePrinter, WritePrinter,EndPagePrinter,EndDocPrinter, ClosePrinter), and none of them indicates an error. Any idea why it happens and how it may be fixed?

PRINTER_DEFAULTS pd;
pd.DesiredAccess = PRINTER_ACCESS_USE;
pd.pDatatype = NULL;
pd.pDevMode = NULL;
if (!OpenPrinter(szPrinterName, &hPrinter, &pd))
return false;

Related

Brother Printer SDK cannot print label ERROR_WRONG_LABEL_ IOS

I am trying to print a label from my Nativescript+Vue application for IOS. I can successfully connect to printer, however when I try to print an image, it will give me an Error. The printer I am using is the "QL-810W". The label that I have loaded is the 62mm roll (no set length) and can support both red and black.
Here is the surrounding code:
exports.BrotherPrinterClass = BrotherPrinterClass;
var BrotherPrinter = (function () {
function BrotherPrinter() {
}
BrotherPrinter.prototype.print_image = function (PrinterName, ipAddress, image) {
try {
let printer = new BRPtouchPrinter()
printer.printerName = PrinterName
printer.interface = CONNECTION_TYPE_WLAN
printer.setIPAddress(ipAddress)
let settings = new BRPtouchPrintInfo()
settings.strPaperName = "62mmRB"
settings.nPrintMode = 0x03 //PRINT_FIT_TO_PAGE
settings.nAutoCutFlag = 0x00000001 //OPTION_AUTOCUT
settings.nOrientation = 0x00 //ORI_LANDSCAPE
settings.nHorizontalAlign = 0x01 //ALIGN_CENTER
settings.nVerticalAlign = 0x01 //ALIGN_MIDDLE
printer.setPrintInfo(settings)
if (printer.startCommunication()) {
//Print to the printer
let errorCode = printer.printImageCopy(image.ios.CGImage, 1);
if (errorCode != 0)
console.log("ERROR - ", errorCode)
}
printer.endCommunication()
}
else{
console.log("Failed to connect")
}
}
catch(e) {
console.log("Error - ", e);
}
};
This then returns the error "-41" which correlates to ERROR_WRONG_LABEL_. I've tried adjusting all properties, I've also tried settings.strPaperName = "62mm" and that didn't return anything different.
The image that is been passed in is of an ImageSource type, and I've also tried the following line, let errorCode = printer.printImageCopy(image.ios, 1);. This returns an error code of 0 which correlates to ERROR_NONE_ But nothing gets printed out. And as far as I know, this isn't a CGImage like the SDK wants.
I can print from an iPad using the "iPrint&Label" app. Just not from the SDK
EDIT:
I have tried to print from a file using printFilesCopy(). This returns no error when I go to print, but nothing gets printed. Additionally, I can set the settings incorrectly (eg, change '62mmRB' for '102mm') or I can outright not set the settings by commenting out the printer.setPrintInfo(settings) line. These changes do not impact the result.
Furthermore, I have purchased a 62mm black only roll, and tried that, only to find that I have the exact same issue.

How can I uniquely identify textbox controls retrieved from another application?

I'm writing an application in C# to remote control another application. I don't have access to that application yet, but it's most likely written in either Visual C++ or VB6. I'm currently remote controlling a simple C# winforms program I created instead. So far, I can get all of the controls in a manner similar to Spy++ using PInvoke. I can also do things like setting the text in a textbox. The problem I have is that I can't uniquely identify a specific text box between executions of the application I'm controlling. I can't use hWnd for example because it's different every time you run the application. GetWindowLong returns whatever the hWnd is so that's no help. The internet speaks of this message called WM_GETCONTROLNAME. The scary code below attempts to use that message to get the name the developer used to uniquely identify the controls. The SendMessage seems like it's returning the number of bytes in the name. But the buffer containing that name comes back all zeros.
So here's the question. How can I fix the code below so that it returns that name correctly? (Hopefully it's a boneheaded mistake that's easy to fix) Or, just as good, is there some other ID that will be guaranteed to be the same every time I run the program? Without something to uniquely identify the textbox controls, my code can't tell them apart.
I do have a hack in mind. It seems to me that using the position of the textboxes to tell them apart would work. But I'd much prefer to have the code below working.
public static string GetWindowText(IntPtr Handle)
{
int BufferSize = 256;
uint Message = RegisterWindowMessage("WM_GETCONTROLNAME");
StringBuilder sb = new StringBuilder(BufferSize);
byte[] ControlName = new byte[BufferSize];
long BytesRead = 0;
IntPtr BytesReadPointer = new IntPtr(BytesRead);
IntPtr OtherMem = IntPtr.Zero;
try
{
OtherMem = VirtualAllocEx(Handle, IntPtr.Zero, new IntPtr(sb.Capacity), AllocationType.Commit, MemoryProtection.ExecuteReadWrite);
var Result = SendMessage(Handle, Message, new IntPtr(sb.Capacity), OtherMem);
//Result contains different numbers which seem like the correct lengths
var Result2 = ReadProcessMemory(Handle, OtherMem, ControlName, BufferSize, out BytesReadPointer);
//ControlName always comes back blank.
}
finally
{
var Result3 = VirtualFreeEx(Handle, OtherMem, BufferSize, FreeType.Release);
}
return ""; // Convert ControlName to a string
}
So after a good night's sleep and a little tinkering, I managed to fix the problem myself. There were two problems. First, I was passing in the handle to the control to VirtualAllocEx. But in the documentation for that function, it says that it takes a handle to the process. So started supplying that. Then VirtualAllocEx was telling me that I had an invalid handle. So it turns out the handle you pass to VirualAllocEx has to have PROCESS_VM_OPERATION set. So I made a call to OpenProcess to get that handle, then passed that to VirtualAllocEx. After that, everything worked. I just had to convert the byte array coming back into a string, and trim a bunch of nulls. This returns the Name property on the controls. I'll post the code here in case anyone else needs something like this.
[Flags]
public enum ProcessAccessFlags : uint
{
All = 0x001F0FFF,
Terminate = 0x00000001,
CreateThread = 0x00000002,
VirtualMemoryOperation = 0x00000008,
VirtualMemoryRead = 0x00000010,
VirtualMemoryWrite = 0x00000020,
DuplicateHandle = 0x00000040,
CreateProcess = 0x000000080,
SetQuota = 0x00000100,
SetInformation = 0x00000200,
QueryInformation = 0x00000400,
QueryLimitedInformation = 0x00001000,
Synchronize = 0x00100000
}
public static string GetWindowText(IntPtr ControlHandle, IntPtr ProcessHandle)
{
int BufferSize = 256;
StringBuilder sb = new StringBuilder(BufferSize);
byte[] ControlNameByteArray = new byte[BufferSize];
long BytesRead = 0;
IntPtr BytesReadPointer = new IntPtr(BytesRead);
IntPtr RemoteProcessMemoryAddress = IntPtr.Zero;
IntPtr RemoteProcessHandle = IntPtr.Zero;
try
{
uint Message = RegisterWindowMessage("WM_GETCONTROLNAME");
RemoteProcessHandle = OpenProcess(ProcessAccessFlags.CreateThread |
ProcessAccessFlags.QueryInformation |
ProcessAccessFlags.VirtualMemoryOperation |
ProcessAccessFlags.VirtualMemoryWrite |
ProcessAccessFlags.VirtualMemoryRead, false, Process.Id);
RemoteProcessMemoryAddress = VirtualAllocEx(RemoteProcessHandle, IntPtr.Zero, new IntPtr(sb.Capacity), AllocationType.Commit, MemoryProtection.ExecuteReadWrite);
if (RemoteProcessMemoryAddress == IntPtr.Zero)
{
throw new Exception("GetWindowText: " + GetLastWin32Error());
}
SendMessage(ControlHandle, Message, new IntPtr(sb.Capacity), RemoteProcessMemoryAddress);
ReadProcessMemory(RemoteProcessHandle, RemoteProcessMemoryAddress, ControlNameByteArray, BufferSize, out BytesReadPointer);
string ControlName = Encoding.Unicode.GetString(ControlNameByteArray).TrimEnd('\0');
return ControlName;
}
finally
{
VirtualFreeEx(RemoteProcessHandle, RemoteProcessMemoryAddress, BufferSize, FreeType.Release);
}
}

Developing iTunes like application in c#

I need to develop an application in c# that could automatically detect an iPhone when it is connected to the system and read a particular file for the iPhone file system. I basically want this file to be downloaded automatically from device to the PC. I used USBpcap tool that suggests that iTunes connects to phone using some XML format. Any help or insight greatly appreciated. Is there any documentation of Third party APIs that can get me started? There are some applications that can replicate iTunes functionality e.g Copytrans
Is there any protocol or APIs provided by Apple?
I have been digging the internet and found this link Layered communication for iPhone.
Also I am using the LibUsbDotNet libraries for communicating to the usb device(Example). Can any one suggest which EndPoints should be used.
It seems to me that I have to implement usbmuxd in windows application. It is a multilayer protocol. There must be some libraries that implement usbmuxd(I dont think I have to implement the protocol all by my self)
I dont have much idea about iTunes communication as well as USB communication. I am adding as much information as I can(of course with the things I come up with in my R&D). Any help is highly appreciated.
public static DateTime LastDataEventDate = DateTime.Now;
public static UsbDevice MyUsbDevice;
#region SET YOUR USB Vendor and Product ID!
public static UsbDeviceFinder MyUsbFinder = new UsbDeviceFinder(1452, 4768);
#endregion
private void LibUSB()
{
ErrorCode ec = ErrorCode.None;
try
{
// Find and open the usb device.
MyUsbDevice = UsbDevice.OpenUsbDevice(MyUsbFinder);
// If the device is open and ready
if (MyUsbDevice == null)
throw new Exception("Device Not Found.");
// If this is a "whole" usb device (libusb-win32, linux libusb)
// it will have an IUsbDevice interface. If not (WinUSB) the
// variable will be null indicating this is an interface of a
// device.
IUsbDevice wholeUsbDevice = MyUsbDevice as IUsbDevice;
if (!ReferenceEquals(wholeUsbDevice, null))
{
// This is a "whole" USB device. Before it can be used,
// the desired configuration and interface must be selected.
// Select config #1
wholeUsbDevice.SetConfiguration(1);
// Claim interface #0.
wholeUsbDevice.ClaimInterface(0);
}
// open read endpoint 1.
UsbEndpointReader reader = MyUsbDevice.OpenEndpointReader(ReadEndpointID.Ep03);
// open write endpoint 1.
UsbEndpointWriter writer = MyUsbDevice.OpenEndpointWriter(WriteEndpointID.Ep02);
int bytesWritten;
ec = writer.Write(usbmux_header.GetBytes(), 2000, out bytesWritten);
if (ec != ErrorCode.None)
throw new Exception(UsbDevice.LastErrorString);
byte[] readBuffer = new byte[1024];
while (ec == ErrorCode.None)
{
int bytesRead;
// If the device hasn't sent data in the last 100 milliseconds,
// a timeout error (ec = IoTimedOut) will occur.
ec = reader.Read(readBuffer, 10000, out bytesRead);
if (ec == ErrorCode.Win32Error)
throw new Exception("port not open");
if (bytesRead == 0)
throw new Exception("No more bytes!");
// Write that output to the console.
Console.Write(Encoding.Default.GetString(readBuffer, 0, bytesRead));
}
}
catch (Exception ex)
{
Console.WriteLine();
Console.WriteLine((ec != ErrorCode.None ? ec + ":" : String.Empty) + ex.Message);
}
finally
{
if (MyUsbDevice != null)
{
if (MyUsbDevice.IsOpen)
{
// If this is a "whole" usb device (libusb-win32, linux libusb-1.0)
// it exposes an IUsbDevice interface. If not (WinUSB) the
// 'wholeUsbDevice' variable will be null indicating this is
// an interface of a device; it does not require or support
// configuration and interface selection.
IUsbDevice wholeUsbDevice = MyUsbDevice as IUsbDevice;
if (!ReferenceEquals(wholeUsbDevice, null))
{
// Release interface #0.
wholeUsbDevice.ReleaseInterface(0);
}
MyUsbDevice.Close();
}
MyUsbDevice = null;
// Free usb resources
UsbDevice.Exit();
}
}
}
class usbmux_header
{
public static UInt32 length = 10; // length of message, including header
public static UInt32 reserved = 0; // always zero
public static UInt32 type = 3; // message type
public static UInt32 tag = 2; // responses to this query will echo back this tag
public static byte[] GetBytes()
{
byte[] lgth = BitConverter.GetBytes(length);
byte[] res = BitConverter.GetBytes(reserved);
byte[] tpe = BitConverter.GetBytes(type);
byte[] tg = BitConverter.GetBytes(tag);
byte[] retArray = new byte[16];
lgth.CopyTo(retArray, 0);
res.CopyTo(retArray, 4);
tpe.CopyTo(retArray, 8);
tg.CopyTo(retArray, 12);
return retArray;
}
};
I have been trying to send hello packet bytes to iPhone but I am not able to read any response from phone.
To play with ipod you can use SharePodLib
As I understand it, only one client can use the USB connection to iOS at one time. On both macOS and Windows, that one client is usbmux. That library multiplexes TCP connections with higher-level clients, including iTunes, Photos, and (on macOS) the open-source peertalk library.
So on Windows, you wouldn't want to implement your own usbmux, but rather a client that sits on top of that, analogous to peertalk. I haven't seen anything open-source that does this, but a number of developers have accomplished it with their own proprietary software.
If anybody else has pointers about using usbmux on Windows, I'd love to hear about it.
—Dave
You can use imobiledevice-net. It provides a C# API to connect to iOS devices using your PC.
For example, to list all iOS devices connected to your PC, you would run something like this:
ReadOnlyCollection<string> udids;
int count = 0;
var idevice = LibiMobileDevice.Instance.iDevice;
var lockdown = LibiMobileDevice.Instance.Lockdown;
var ret = idevice.idevice_get_device_list(out udids, ref count);
if (ret == iDeviceError.NoDevice)
{
// Not actually an error in our case
return;
}
ret.ThrowOnError();
// Get the device name
foreach (var udid in udids)
{
iDeviceHandle deviceHandle;
idevice.idevice_new(out deviceHandle, udid).ThrowOnError();
LockdownClientHandle lockdownHandle;
lockdown.lockdownd_client_new_with_handshake(deviceHandle, out lockdownHandle, "Quamotion").ThrowOnError();
string deviceName;
lockdown.lockdownd_get_device_name(lockdownHandle, out deviceName).ThrowOnError();
deviceHandle.Dispose();
lockdownHandle.Dispose();
}

Emulators Providing Different Results

I am developing an application on windows phone with version 7.1 set as my target build. The problem i am having is that one of the listviews in on of my pages refus to display.
I have debugged to ensure the list gets parsed with contents inside of it . Also the application runs fine when i use a windows 8 emulator. But the same technique used in populating other listviews in other pages of the application work fine on all emulators aprt from this single page that does not display.
I even tried to set the colour of the binding stack panel to see if it will show up and it does but without any content.
I am really confused and my code is very perfect. I wonder if any one has seem this issue before with windows phone emulators?
private void countdownClient_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
{
HtmlDocument doc = new HtmlDocument();
if (e.Error != null)
{
//MessageBox.Show(e.Error.InnerException.Message + "\n Ensure You Have A Working Internet Connection");
return;
}
doc.LoadHtml(e.Result);
String noCountdown = "<div><span>Sorry no buses are expected within 30 minutes of this stop. Please try again later or go to www.tfl.gov.uk</span></div>";
if (e.Result.Contains(noCountdown))
{
//No Buses Expected;
return;
}
else
{
HtmlNode stopCountdownNode;
try
{
stopCountdownNode = doc.DocumentNode.SelectSingleNode("//*[contains(#id, 'stopBoard')]").SelectSingleNode("tbody");
}
catch (Exception)
{
MessageBox.Show("Error Responce From Server");
return;
}
if (stopCountdownNode != null)
{
HtmlNodeCollection countdownNodeList = stopCountdownNode.SelectNodes("tr");
CountDownListBox.ItemsSource = GetCountdownList(countdownNodeList);
}
}
}
private ObservableCollection<BusCountdown> GetCountdownList(HtmlNodeCollection countdownNodeList)
{
ObservableCollection<BusCountdown> countdownList = new ObservableCollection<BusCountdown>();
foreach (HtmlNode countDown in countdownNodeList)
{
String busName = HttpUtility.HtmlDecode(countDown.SelectSingleNode("*[contains(#class, 'resRoute')]").InnerHtml);
String busDestination = HttpUtility.HtmlDecode(countDown.SelectSingleNode("*[contains(#class, 'resDir')]").InnerHtml);
String countDownTime = HttpUtility.HtmlDecode(countDown.SelectSingleNode("*[contains(#class, 'resDue')]").InnerHtml);
countdownList.Add(new BusCountdown(busName, busDestination, countDownTime));
}
return countdownList;
}
public string GetRandomSlash()
{
Random r = new Random();
String slash = "";
int rand = r.Next(1, 20);
for (int i = 0; i < rand; i++)
{
slash += "/";
}
return slash;
}
Try setting your class access specifier which you use to bind to public and give it a try. Let me know if it works.
For ex:
public class Bindingclass
{
public string Name{get;set;}
}
Try using Expression Blend and also delete your previous solution file and build a new solution.
Also set the build action property properly for all pages.
Update your SDK to 7.8 version. You will get multiple choices for Emulators - Emulator 7.1 (256 MB), Emulator 7.1 (512 MB), Emulator 7.8 (256 MB), Emulator 7.8 (512 MB). Test it on all these versions and check output on each Emulator type.
I hope at least one of these helps you get things get working. Let us know.

Looking for a reliable mapping of Forms.Screen.DeviceName to Monitor EDID info

I'm developing an application which will display information derived from the EDID blocks (monitor model, ID, S/N, etc.) on a dialog on the corresponding monitor.
This code works for finding the EDID information for displays. It extracts the EDID information by enumerating the DISPLAY keys under HKLM\SYSTEM\CurrentControlSet\Enum\DISPLAY\[Monitor]\[PnPID]\Device Parameters\EDID.
Update: The above code is relying on "side effects" of PnP use of the registry. I am now using the SetupAPI to enumerate monitors, which correctly handles monitors being attached/removed (unlike the code from the link above.)
I am trying to correlate each Screen in Windows.Forms.Screen.AllScreens[] (\\.\DISPLAY1, \\.\DISPLAY2, etc.) with the entries returned from the above registry inspection.
Note: In the code block below, DisplayDetails.GetMonitorDetails() has now been replaced with more robust registry enumeration code using the SetupAPI, but the data returned is the same.
e.g.
private void Form1_Load(object sender, EventArgs e)
{
Console.WriteLine("Polling displays on {0}:", System.Environment.MachineName);
int i = 0;
foreach ( DisplayDetails dd in DisplayDetails.GetMonitorDetails())
{
Console.WriteLine( "Info: Model: {0}, MonitorID: {1}, PnPID: {2}, Serial#:{3}", dd.Model, dd.MonitorID, dd.PnPID, dd.SerialNumber );
Console.WriteLine( "Does this correlate to Screen: {0}?", Screen.AllScreens[i++].DeviceName );
}
}
Output:
Info: Model: DELL P2411H, MonitorID: DELA06E, PnPID: 5&2e2fefea&0&UID1078018, Serial#:F8NDP0C...PU
Does this correlate to Screen: \\.\DISPLAY1?
Info: Model: DELL P2411H, MonitorID: DELA06E, PnPID: 5&2e2fefea&0&UID1078019, Serial#:F8NDP0C...AU
Does this correlate to Screen: \\.\DISPLAY2?
Answer: NO
In testing, I've found these don't reliably correlate (I have a system in which the first display enumerated is \\.\DISPLAY2).
My Question:
Is there a way to reliably get the EDID information for a given Forms.Screen? I can get the EDID block, but have found no path to correlate this up to the UI top-level Form. Prompting the user is undesirable, as in my use case the two (or more) monitors will likely be the same model and resolution, and only differ by a few digits in the S/N.
I've looked for paths following the Forms.Screen API, Win32 EnumDisplay, other registry GUIDs (PnP and driver-related), but haven't found any promising paths.
I have also investigated the WMI Win32_DesktopMonitor API (Windows 7), however it doesn't appear to have any more information that would help me correlate it to the Windows.Forms.Screen.AllScreens[] entries.
I suspect if there is a way to do this, it's through the SetupAPI, however I haven't found it yet.
A method to resolve the GDI to SetupAPI is available in the EnumDisplayDevices API. If you pass in the EDD_GET_DEVICE_INTERFACE_NAME in for dwFlags, the monitor enumeration will return DeviceID information of the form:
Monitor 0 info:
DeviceName: \\.\DISPLAY1
MonitorInfo: Dell P2411H(Digital)
DeviceID: \\?\DISPLAY#DELA06E#5&2e2fefea&0&UID1078018#{e6f07b5f-ee97-4a90-b076-3
3f57bf4eaa7}
Monitor 1 info:
DeviceName: \\.\DISPLAY2
MonitorInfo: Dell P2411H(Digital)
DeviceID: \\?\DISPLAY#DELA06E#5&2e2fefea&0&UID1078019#{e6f07b5f-ee97-4a90-b076-3
3f57bf4eaa7}
The DeviceID fields now match the results from the didd.DevicePath, as retrieved in the C# fragment below:
Guid MonitorGUID = new Guid(Win32.GUID_DEVINTERFACE_MONITOR);
// We start at the "root" of the device tree and look for all
// devices that match the interface GUID of a monitor
IntPtr h = Win32.SetupDiGetClassDevs(ref MonitorGUID, IntPtr.Zero, IntPtr.Zero, (uint)(Win32.DIGCF_PRESENT | Win32.DIGCF_DEVICEINTERFACE));
if (h.ToInt64() != Win32.INVALID_HANDLE_VALUE)
{
bool Success = true;
uint i = 0;
while (Success)
{
// create a Device Interface Data structure
Win32.SP_DEVICE_INTERFACE_DATA dia = new Win32.SP_DEVICE_INTERFACE_DATA();
dia.cbSize = (uint)Marshal.SizeOf(dia);
// start the enumeration
Success = Win32.SetupDiEnumDeviceInterfaces(h, IntPtr.Zero, ref MonitorGUID, i, ref dia);
if (Success)
{
// build a DevInfo Data structure
Win32.SP_DEVINFO_DATA da = new Win32.SP_DEVINFO_DATA();
da.cbSize = (uint)Marshal.SizeOf(da);
// build a Device Interface Detail Data structure
Win32.SP_DEVICE_INTERFACE_DETAIL_DATA didd = new Win32.SP_DEVICE_INTERFACE_DETAIL_DATA();
didd.cbSize = (uint)(4 + Marshal.SystemDefaultCharSize); // trust me :)
// now we can get some more detailed information
uint nRequiredSize = 0;
uint nBytes = Win32.BUFFER_SIZE;
if (Win32.SetupDiGetDeviceInterfaceDetail(h, ref dia, ref didd, nBytes, out nRequiredSize, ref da))
{
// Now we get the InstanceID
IntPtr ptrInstanceBuf = Marshal.AllocHGlobal((int)nBytes);
Win32.CM_Get_Device_ID(da.DevInst, ptrInstanceBuf, (int)nBytes, 0);
string InstanceID = Marshal.PtrToStringAuto(ptrInstanceBuf);
Console.WriteLine("InstanceID: {0}", InstanceID );
Marshal.FreeHGlobal(ptrInstanceBuf);
Console.WriteLine("DevicePath: {0}", didd.DevicePath );
}
i++;
}
}
}
Win32.SetupDiDestroyDeviceInfoList(h);
}
Sample Output:
InstanceID: DISPLAY\DELA06E\5&2E2FEFEA&0&UID1078018
DevicePath: \\?\display#dela06e#5&2e2fefea&0&uid1078018#{e6f07b5f-ee97-4a90-b076-33f57bf4eaa7}
The DeviceName from the original EnumDisplayDevices matches the Forms.Screen.DeviceName property.
With these two pieces of information, it is now possible to read the EDID block during the SetupDIEnumDeviceInterface traversal using a fragment like the below:
private static byte[] GetMonitorEDID(IntPtr pDevInfoSet, SP_DEVINFO_DATA deviceInfoData)
{
IntPtr hDeviceRegistryKey = SetupDiOpenDevRegKey(pDevInfoSet, ref deviceInfoData,
DICS_FLAG_GLOBAL, 0, DIREG_DEV, KEY_QUERY_VALUE);
if (hDeviceRegistryKey == IntPtr.Zero)
{
throw new Exception("Failed to open a registry key for device-specific configuration information");
}
IntPtr ptrBuff = Marshal.AllocHGlobal((int)256);
try
{
RegistryValueKind lpRegKeyType = RegistryValueKind.Binary;
int length = 256;
uint result = RegQueryValueEx(hDeviceRegistryKey, "EDID", 0, ref lpRegKeyType, ptrBuff, ref length);
if (result != 0)
{
throw new Exception("Can not read registry value EDID for device " + deviceInfoData.ClassGuid);
}
}
finally
{
RegCloseKey(hDeviceRegistryKey);
}
byte[] edidBlock = new byte[256];
Marshal.Copy(ptrBuff, edidBlock, 0, 256);
Marshal.FreeHGlobal(ptrBuff);
return edidBlock;
}
Which, finally, can be parsed for the VESA descriptor blocks, as shown in the DisplayDetails.GetMonitorDetails() method in this code.

Resources