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

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);
}
}

Related

Importing native methods in Xamarin.Mac

I'm looking for a way to use SCNetworkInterfaceCopyAll in my Xamarin.Mac app.
I've imported it
[DllImport("/System/Library/Frameworks/SystemConfiguration.framework/SystemConfiguration")]
public static extern IntPtr SCNetworkInterfaceCopyAll();
Then I get an array by calling var array = NSArray.ArrayFromHandle<NSObject>(pointer).
But can't figure out how to get values from the output array of SCNetworkInterface. I've tried to marshal it as
[StructLayout(LayoutKind.Sequential)]
public struct Test
{
IntPtr interface_type;
IntPtr entity_hardware;
}
And then call Marshal.PtrToStructure<Test>(i.Handle) but it gives random pointers instead of meaningful values.
Can you use the information provided from System.Net.NetworkInformation. NetworkInterface (or do you need actually need a SCNetworkInterface?)
Xamarin.Mac Example:
foreach (NetworkInterface nic in NetworkInterface.GetAllNetworkInterfaces())
{
if (nic.OperationalStatus == OperationalStatus.Up)
Console.WriteLine(nic.GetPhysicalAddress());
}
Looking at SCNetworkConfiguration.h it appears there are a number of C APIs you are to call on your IntPtr to retrieve the specific information you need.
CoreFoundation APIs often return "baton" pointers that you need to pass to other functions. Where did you see that struct definition?
You can use Objective-C methods in Xamarin to get the MAC Address as C# gives a different MAC Address:
[DllImport("/System/Library/Frameworks/SystemConfiguration.framework/SystemConfiguration")]
public static extern IntPtr SCNetworkInterfaceCopyAll();
[DllImport("/System/Library/Frameworks/SystemConfiguration.framework/SystemConfiguration")]
public static extern IntPtr SCNetworkInterfaceGetHardwareAddressString(IntPtr scNetworkInterfaceRef);
private string MacAddress()
{
string address = string.Empty;
using (var interfaces = Runtime.GetNSObject<NSArray>(SCNetworkInterfaceCopyAll()))
{
for (nuint i = 0; i < interfaces.Count; i++)
{
IntPtr nic = interfaces.ValueAt(i);
var addressPtr = SCNetworkInterfaceGetHardwareAddressString(nic);
address = Runtime.GetNSObject<NSString>(addressPtr);
if (address != null) break;
}
}
return address;
}

Print labels via a redirected printer

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;

How to make Xamarin.Mac app "Open at Login"?

I have a Xamarin.Mac app that needs to open automatically at login.
How do I have my application get this setting without having to manually click on it?
I can give you a hint how to do it programmatically.
For this approach you need to make use of calls to native libraries via DllImport.
Following code will give you an idea how to proceed:
//needed library
const string DllName = "/System/Library/Frameworks/ApplicationServices.framework/ApplicationServices";
static LSSharedFileList ()
{
dllHandle = Dlfcn.dlopen (DllName, 0);
kLSSharedFileListSessionLoginItems = Dlfcn.GetStringConstant (dllHandle, "kLSSharedFileListSessionLoginItems");
kLSSharedFileListItemLast = Dlfcn.GetStringConstant (dllHandle, "kLSSharedFileListItemLast");
}
[DllImport(DllName)]
public static extern IntPtr LSSharedFileListCreate (
IntPtr inAllocator,
IntPtr inListType,
IntPtr listOptions);
[DllImport(DllName, CharSet=CharSet.Unicode)]
public static extern void CFRelease (
IntPtr cf
);
[DllImport(DllName)]
public extern static IntPtr LSSharedFileListInsertItemURL (
IntPtr inList,
IntPtr insertAfterThisItem,
IntPtr inDisplayName,
IntPtr inIconRef,
IntPtr inURL,
IntPtr inPropertiesToSet,
IntPtr inPropertiesToClear);
And here the actual snippet:
public static void EnableLogInItem ()
{
IntPtr pFileList = IntPtr.Zero;
IntPtr pItem = IntPtr.Zero;
try
{
pFileList = LSSharedFileListCreate (
IntPtr.Zero,
kLSSharedFileListSessionLoginItems,
IntPtr.Zero);
pItem = LSSharedFileListInsertItemURL (
pFileList,
kLSSharedFileListItemLast,
IntPtr.Zero,
IntPtr.Zero,
NSBundle.MainBundle.BundleUrl.Handle,
IntPtr.Zero,
IntPtr.Zero);
}
finally
{
CFRelease (pItem);
CFRelease (pFileList);
}
}
Please keep in mind that it is not the whole solution, it is just a snippet to put an app in the login items list. Of course you have to handle errors, check for IntPtr.Zero after every call and so on, but this should give you an idea how it works.
Hope that helps!
Since the LSSharedFileList library is not supported on Xamarin.Mac you have to create a dylib with Xcode and bind it in your Xamarin.Mac application.
1) Create a Dylib project on Xcode. Add This function:
-(BOOL)AddLoginItem:(NSString *) AppPath{
// Your have to send this string as argument(i.e: on a textbox, write: /Applications/Calculator.app/)
// Get Login Items
LSSharedFileListRef loginItems = LSSharedFileListCreate(NULL, kLSSharedFileListSessionLoginItems, NULL);
if (loginItems) {
NSLog(#"[DEBUG]Your Application Path:%#",AppPath);
// Covert String to CFURLRef (It adds "file://" to your itemUrl1)
CFURLRef appUrl = (__bridge CFURLRef)[NSURL fileURLWithPath:(NSString*) AppPath];
NSLog(#"[DEBUG] appUrl:%#", appUrl);
// Now we add the requested Login Item
LSSharedFileListItemRef itemRef = LSSharedFileListInsertItemURL(loginItems, kLSSharedFileListItemLast, NULL, NULL, appUrl, NULL, NULL);
// Confirm that your path was correct and that you got a valid Login Item Reference
NSLog(#"[DEBUG]Item:%#", itemRef);
if (itemRef) CFRelease(itemRef);
// Your item just got added to the user's Login Items List
CFRelease(loginItems);
return true;
}
return false;
}
2) Create a Cocoa project with an TextBox, a Push button and their Actions and Outlets controls in it.
Use this link to help you with the C# bindings from a objective-c DLL.
3) On your Xamarin.Mac project, in MainWindow.cs, add the following code and make the necessary changes to fit on your code.
Don't forget to add your .Net Assembly reference, created on the previous link and also your: using DLLloginItem; line.
// Some variables
private string TestItemName;
private bool rem;
// Button Click
partial void AddItemButton (Foundation.NSObject sender) {
LoginItemsDLL loginItem = new LoginItemsDLL();
// Enter this string on your TextBox TxtName /Applications/Calculator.app/
TestItemName = TxtName.StringValue;
rem=loginItem.AddLoginItem(TestItemName);
Console.WriteLine(rem);
}
In order to get your application path entered, use another function that just receives the application name and returns its path into AddLoginItem argument. Hope it helps!

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.

Initiate key presses in Cocoa

Im writing a server to receive key events from an iPhone. I can send a message from the iPhone and have my server display it, on the Mac, now i just need to translate that into a Key press, and simulate that press in Cocoa.
Could anyone offer me a starting point, as i guess this is quite low level.
Thanks
I believe IOHIDPostEvent may be what you're looking for. Something like this:
static void HIDPostVirtualKey(
const UInt8 inVirtualKeyCode,
const Boolean inPostUp,
const Boolean inRepeat)
{
NXEventData event;
IOGPoint loc = { 0, 0 };
bzero(&event, sizeof(NXEventData));
event.key.repeat = inRepeat;
event.key.keyCode = inVirtualKeyCode;
event.key.origCharSet = event.key.charSet = NX_ASCIISET;
event.key.origCharCode = event.key.charCode = 0;
IOHIDPostEvent( get_event_driver(), inPostUp ? NX_KEYUP : NX_KEYDOWN, loc, &event, kNXEventDataVersion, kIOHIDPostHIDManagerEvent, FALSE );
}

Resources