I am using the ManagedWindows API in a C# environment:
http://mwinapi.sourceforge.net/
In the past I have successfully scraped the contents of listbox-like parts of other running programs using the code below, where I iterate through key/value pairs to find the list items. For this particular list of items, however, I can get an accurate number of items, but the value is always null!
Using this:
TargetMidWindow.Content.ComponentType
I have discovered that the list I am having issues with is a 'listview' whereas the other windows I have had success with are 'detailslistview' in case it matters. Below is the code I have for finding the data I want, which is almost identical to my other successful code with the exception of altering the search terms I used. Also, in case its relevant, the program I'm trying to pull data out of is MetaTrader4, and I've been able to scrape data off other parts of the program successfully.
// Find the main window
SystemWindow[] TopLevel = SystemWindow.AllToplevelWindows;
SystemWindow TargetTopWindow = SystemWindow.ForegroundWindow;
foreach (SystemWindow SearchWindow in TopLevel)
{
string Title = SearchWindow.Title;
if (Title.Contains("MetaTrader"))
{
TargetTopWindow = SearchWindow;
break;
}
}
// Find the section where positions are contained
SystemWindow[] MidLevel = TargetTopWindow.AllDescendantWindows;
SystemWindow TargetMidWindow = SystemWindow.ForegroundWindow;
foreach (SystemWindow SearchWindow in MidLevel)
{
string ClassName = SearchWindow.ClassName;
if (ClassName.Contains("SysListView32"))
{
SystemWindow ParentWindow = SearchWindow.Parent;
if ((ParentWindow.Title.Contains("Terminal")))
{
TargetMidWindow = SearchWindow;
}
}
}
// Get the positions
Dictionary<string, string> RawValues = new Dictionary<string, string>();
foreach (KeyValuePair<string, string> KVP in TargetMidWindow.Content.PropertyList)
{
string key = KVP.Key;
string value = KVP.Value;
}
Is there something special I need to do so that I do not get 'null' values for each list item?
Thanks!
Bill
Hmya, wrapping Windows messages with a friendly API isn't that difficult. Windows Forms would be a good example. But that has a knack for running into a very solid wall once you start doing this with another process.
The specific message you need in order to read ListView items is LVM_GETITEM. That's one of those solid wall messages. The LPARAM argument you pass to SendMessage() needs to be a pointer to an LVITEM structure. The control fills in the fields in that structure. Problem is, the pointer you pass is only valid in your process, not the process who owns that window.
Fixing this takes a great deal of hackery. You have to allocate memory that's valid inside that process. That takes VirtualAllocEx() and ReadProcessMemory(). Plus all the glue calls you need to make these work. I assume that this library you are using is not taking care of this. Easy to find out, grep the source code files for these API function names.
If you want to find the correct handle to a particular SysListView32 window, you need to start with the right window hierarchy. From the code snippet, it doesn't appear that you're actually finding the correct handle to retrieve a quote from the SysListView32 window. This is why you're receiving null values back. You would do well to run spy++ and determine the correct windows structure of the Metatrader terminal for your specific broker and build. I've found that the classes are different between builds for some of the windows, and also between some brokers, though to a lesser extent.
You're looking for the specific quote window hierarchy like this:
Metatrader -> Market Watch -> Market Watch -> SysListView32
By contrast, currently you're looking here in your code:
Metatrader -> Terminal -> (many sub-windows with SysListView32 class)
Where each level to the right is a child window of the window to the left.
Find the parent "Metatrader" window then chain down looking for the child window until you get to SysListView32. If you use spy++ you can read the class for the SysListView32 parent window (market watch), and use that to enumerate the windows to find the correct SysListView32 window. FYI the correct Market Watch class name for build 419 is:
Afx:00400000:b:00010003:00000000:00000000
Once you find the correct window, you may be able to extract its contents using your current component. I haven't tried that and am looking to port some code from VB6 from a ListView module that does in fact involve epic hackery. ;) I may take a look at the .NET Managed Windows API to see if this can help make the process simpler.
But in the mean time, if you do have to go low-level, the following VB6 source should help you get an idea of what is involved. This is fairly advanced material so good luck!
Public Function GetListviewItem(ByVal hWindow As Long, ByVal pColumn As Long, ByVal pRow As Long) As String
Dim result As Long
Dim myItem As LV_ITEMA
Dim pHandle As Long
Dim pStrBufferMemory As Long
Dim pMyItemMemory As Long
Dim strBuffer() As Byte
Dim index As Long
Dim tmpString As String
Dim strLength As Long
Dim ProcessID As Long, ThreadID As Long
ThreadID = GetWindowThreadProcessId(hWindow, ProcessID)
'**********************
'init the string buffer
'**********************
ReDim strBuffer(MAX_LVMSTRING)
'***********************************************************
'open a handle to the process and allocate the string buffer
'***********************************************************
pHandle = OpenProcess(PROCESS_VM_OPERATION Or PROCESS_VM_READ Or PROCESS_VM_WRITE, False, ProcessID)
pStrBufferMemory = VirtualAllocEx(pHandle, 0, MAX_LVMSTRING, MEM_COMMIT, PAGE_READWRITE)
'************************************************************************************
'initialize the local LV_ITEM structure
'The myItem.iSubItem member is set to the index of the column that is being retrieved
'************************************************************************************
myItem.mask = LVIF_TEXT
myItem.iSubItem = pColumn
myItem.pszText = pStrBufferMemory
myItem.cchTextMax = MAX_LVMSTRING
'**********************************************************
'write the structure into the remote process's memory space
'**********************************************************
pMyItemMemory = VirtualAllocEx(pHandle, 0, Len(myItem), MEM_COMMIT, PAGE_READWRITE)
result = WriteProcessMemory(pHandle, pMyItemMemory, myItem, Len(myItem), 0)
'*************************************************************
'send the get the item message and write back the memory space
'*************************************************************
result = SendMessage(hWindow, LVM_GETITEMTEXT, pRow, ByVal pMyItemMemory)
result = ReadProcessMemory(pHandle, pStrBufferMemory, strBuffer(0), MAX_LVMSTRING, 0)
result = ReadProcessMemory(pHandle, pMyItemMemory, myItem, Len(myItem), 0)
'**************************************************
'turn the byte array into a string and send it back
'**************************************************
For index = LBound(strBuffer) To UBound(strBuffer)
If Chr(strBuffer(index)) = vbNullChar Then Exit For
tmpString = tmpString & Chr(strBuffer(index))
Next index
tmpString = Trim(tmpString)
'**************************************************
'deallocate the memory and close the process handle
'**************************************************
result = VirtualFreeEx(pHandle, pStrBufferMemory, 0, MEM_RELEASE)
result = VirtualFreeEx(pHandle, pMyItemMemory, 0, MEM_RELEASE)
result = CloseHandle(pHandle)
If Len(tmpString) > 0 Then GetListviewItem = tmpString
End Function
Related
I am trying to create a MMF-backed collection that dynamically expands up to 2GB. I wanted to be able to read existing items at the same time new items are being added. It works great on my development machine but I am getting an error on some machines:
The thread tried to read from or write to a virtual address for which it does not have the appropriate access.
Has anyone seen this error before? I am likely doing something wrong with the way I am handling MemoryMappedFiles, I'm creating a 2GB MMF with the DelayAllocatePages flag so it doesn't use it all right away:
public const long ONE_GIGABYTE = 1073741824;
long maxCapacity = 2 * ONE_GIGABYTE;
mmStorage = MemoryMappedFile.CreateNew(mmfName, maxCapacity, MemoryMappedFileAccess.ReadWrite, MemoryMappedFileOptions.DelayAllocatePages, null, HandleInheritability.Inheritable);
Then I write data to it a chunk at a time, using an internal collection to keep track of where each chunk is located.
lock ( writeLock ) {
// get the most recently added item
var lastItem = _itemLocations[_itemLocations.Count - 1];
// calculate next items offset
long newOffset = lastItem.Offset + lastItem.Length;
// add next items data
using ( var mmStream = mmStorage.CreateViewStream(newOffset, itemBytes.Length, MemoryMappedFileAccess.ReadWrite) ) {
Trace.WriteLine(string.Format("Writing {0} bytes at location {1}", itemBytes.Length, newOffset));
mmStream.Write(itemBytes, 0, itemBytes.Length);
}
// add location info to list
_itemLocations.Add(new ItemLocation()
{
Offset = newOffset,
Length = itemBytes.Length
});
}
On the remote machine the first write goes ok, but the second write is causing the exception I mentioned which kills the program completely.
Writing 5973 bytes at location 0
Writing 5901 bytes at location 5973
The thread tried to read from or write to a virtual address for which it does not have the appropriate access.
Update
I have tried changing
MemoryMappedFileOptions.DelayAllocatePages
to
MemoryMappedFileOptions.None
and it stops throwing the exception, but it also allocates the full 2GB right away. I'd prefer if I could grow the MMF as needed but I guess it wont work on all machines. I'm not sure why DelayAllocatePages works on some machines and not others.
I'm using the TraceEvent library to capture ETW traces, but I'm not able to determine the name of the process that caused an event.
Here is what I have so far:
var session = new TraceEventSession(sessionName, null);
session.EnableProvider(MyEventSource.Log.Guid, TraceEventLevel.Informational,
options: TraceEventOptions.Stacks);
Task.Delay(1000).ContinueWith(t => session.Stop()); // for testing, deal with it (⌐■_■)
var src = new ETWTraceEventSource(sessionName, TraceEventSourceType.Session);
TraceLog.CreateFromSource(src, etlxFile, null);
var log = TraceLog.OpenOrConvert(etlxFile);
var process = log.Events.First().ProcessName;
// breakpoint
When the breakpoint at the end is hit, process is "". ProcessID is a proper PID, but that's the only useful information I could find from the processes in the log.
I expected process names to be captured by the log. Am I doing something wrong, or is this API just not available on my OS (Windows 7)?
I truly believe that process name is not being captured by the ETW log. Etw system event contains only process ID field. Although TraceEvent library declares this one as a part of TraceEvent, this one actually is being populated based on executable image filename and process ID, which is implemented differently for all 4 TraceEventSource implementations.
Another observation is that I was never able to have this one populated (my OS is Windows 8.1).
The simple repro is to use SimpleEventSourceMonitor sample from Microsoft TraceEvent Library Samples package.
If you suspect that this is an issue, then it is better ask its owners Vance Morrison and Cosmin Radu.
This can be done by enabling the kernel provider, and then maintaining a lookup of process id to process name. Here's a rough example - no error checking, but you get the idea.
// create a lookup collection for future use
var pidToProcessName = new Dictionary<int, string>();
var session = new TraceEventSession(...);
// enable the kernel provider - note! this most come first
session.EnableKernelProvider(KernelTraceEventParser.Keywords.Process);
...
session.Source.Kernel.ProcessStart += ProcessStart;
session.Source.Dynamic.All += TraceEvent;
...
session.Source.Procces();
void ProcessStart(ProcessTraceData obj)
{
if(obj.OpCode == TraceEventOpcode.Start)
{
pidToProcessName[obj.ProcessID] = obj.ProcessName;
}
}
void TraceEvent(TraceEvent obj)
{
// pull the process name from our lookup
var processNameOfEvent = pidToProcessName[obj.ProcessID];
}
People claim the following VB script works for changing network adapter names. However I am having a decidedly difficult time trying to convert this to a c# appliaction that can do the same thing. The problem I seem to be facing is that calls to the NetworkInterface.Name is readonly.
Option Explicit
Const NETWORK_CONNECTIONS = &H31&
Dim sOldName= WScript.Arguments(0)
Dim sNewName= WScript.Arguments(1)
Dim objShell, objFolder, colItems, objItem
Set objShell = CreateObject("Shell.Application")
Set objFolder = objShell.Namespace(NETWORK_CONNECTIONS)
Set colItems = objFolder.Items
For Each objItem in colItems
If objItem.Name = sOldName Then
objItem.Name =sNewName
End If
Next
I found this which explains it a bit more: http://blogs.technet.com/b/heyscriptingguy/archive/2005/05/11/how-can-i-rename-a-local-area-connection.aspx.
Ok, so there are special folders where the NIC names are stored and you access those folders by binding to the them via the SHELL. How then do you do something like this in c#?
You can change the name of a NIC easily through the registry if you know how the registry structure works.
You will need the NetworkAdapters GUID in order to locate which path to open. To get the network adapter GUID I recommend first querying the WMI "Win32_NetworkAdapter" class. There is a GUID property along with all the other properties needed to identify specific adapters.
You will notice this GUID in the registry path: {4D36E972-E325-11CE-BFC1-08002BE10318}Visit link for information on it:
http://technet.microsoft.com/en-us/library/cc780532(v=ws.10).aspx
string fRegistryKey = string.Format(#"SYSTEM\CurrentControlSet\Control\Network\{4D36E972-E325-11CE-BFC1-08002BE10318}\{0}\Connection", NIC_GUID);
RegistryKey RegistryKey = RegistryKey.OpenRemoteBaseKey(RegistryHive.LocalMachine, #"\\" + Server.Name);
RegistryKey = RegistryKey.OpenSubKey(fRegistryKey, true); //true is for WriteAble.
RegistryKey.SetValue("Name", "<DesiredAdapterName>");
By design the windows UI will not allow for duplicate NIC names. However, you can force duplicate NIC names via the registry. We have done tests, there seem to be nothing critically effected by having duplicate names. Windows seems to still function fine. You just want to be wary about scripting against NIC names if you don’t incorporate anti-duplicate name logic.
To create uniqueness you can use the adapter index property associated with the WMI query.
You can use the System.Management assembly and use this class.
Follow the sample here - http://social.msdn.microsoft.com/Forums/en-US/csharplanguage/thread/727c8766-8189-4ad6-956d-958e52b97c05/
You can also create a VB.NET dll with the functionality you need and reference and call it from your C# code.
Here is a console app demonstrating the code (I tested and it works :)
Option Explicit On
Module Module1
Sub Main()
Const NETWORK_CONNECTIONS = &H31&
Dim sOldName = "Local Area Connection"
Dim sNewName = "Network"
Dim objShell, objFolder, colItems, objItem
objShell = CreateObject("Shell.Application")
objFolder = objShell.Namespace(NETWORK_CONNECTIONS)
colItems = objFolder.Items
For Each objItem In colItems
Console.WriteLine(objItem.Name)
If objItem.Name = sOldName Then
objItem.Name = sNewName
End If
Console.WriteLine(objItem.Name)
Next
End Sub
End Module
It prints out:
Local Area Connection
Network
I am trying to use the mixerGetLineInfo and mixerGetLineControls functions to get access to the the volume control for the default recording device opened with waveIn. I have written C# interop code that can successfully enumerate through all the sources destinations and controls in the system, but working out which is the control associated with the default waveIn device has so far eluded me. Does anyone have some sample code that does this?
You could use:
int mixerId = -1;
int inputID = MmeMixerApi.WAVE_MAPPER; // = -1
int result = MmeMixerApi.mixerGetID(inputId, ref mixerId, MIXER_OBJECTFLAG.WAVEIN);
The default input and output devices can be accessed through the wave mapper which has an ID of -1. mixerGetID will return the mixer ID associated with that input. You can then use the mixer ID to iterate over the controls. You would still need to find the correct source line (e.g. microphone, line-in etc.). For this you may want to look for a source line with a particular dwComponentType such as MIXERLINE_COMPONENTTYPE.SRC_MICROPHONE or MIXERLINE_COMPONENTTYPE.SRC_LINE.
Is it possible to enable a second monitor programatically and extend the Windows Desktop onto it in C#? It needs to do the equivalent of turning on the checkbox in the image below.
MSDN Device Context Functions
What you basically need to do:
Use the EnumDisplayDevices() API call
to enumerate the display devices on
the system and look for those that
don't have the
DISPLAY_DEVICE_ATTACHED_TO_DESKTOP
flag set (this will include any
mirroring devices so not all will be
physical displays.) Once you've found
the display device you'll need to get
a valid display mode to change it to,
you can find this by calling the
EnumDisplaySettingsEx() API call -
Generally you'd display all the
available modes and allow the user to
choose however in your case it sounds
like this may be possible to hard-code
and save you an additional step. For
the sake of future-proofing your
application though I'd suggest having
this easily changeable without having
to dig through the source every time,
a registry key would be the obvious
choice. Once you've got that sorted
out populate a DevMode display
structure with the information about
the display positioning (set the
PelsWidth/Height, Position,
DisplayFrequency and BitsPerPel
properties) then set these flags in
the fields member. Finally call
ChangeDisplaySettingsEx() with this
settings structure and be sure to send
the reset and update registry flags.
That should be all you need, hope this
helps,
DISPLAY_DEVICE structure import using PInvoke
EnumDisplayDevices function import
EnumDisplaySettingsEx function import
etc. the rest of them functions can be found with a simple search by name.
If you have windows 7, then just start a process:
private static Process DisplayChanger = new Process
{
StartInfo =
{
CreateNoWindow = true,
WindowStyle = ProcessWindowStyle.Hidden,
FileName = "DisplaySwitch.exe",
Arguments = "/extend"
}
};
then DisplayChanger.Start();
I don't have the full answer here but I am almost sure that you will have to call out of .Net to do this. You will have to use Pinvoke to call an unmanaged dll. A great resource for this is pinvoke.net.
I did a quick search and found http://www.pinvoke.net/default.aspx/user32/ChangeDisplaySettings.html which probably isn't exactly what you want but you will probably find it somewhere on pinvoke.net
I am looking for the same solution. I have written the following code to call ChangeDisplaySettingsEx with PInvoke:
DEVMODE dm = new DEVMODE();
dm.dmSize = (short)Marshal.SizeOf(dm);
dm.dmPelsWidth = 1680;
dm.dmPelsHeight = 1050;
dm.dmBitsPerPel = 32;
dm.dmDisplayFrequency = 60;
dm.dmFields = DevModeFields.DM_BITSPERPEL | DevModeFields.DM_PELSWIDTH |
DevModeFields.DM_PELSHEIGHT | DevModeFields.DM_DISPLAYFREQUENCY;
int res = ChangeDisplaySettingsEx(#"\\.\DISPLAY2", ref dm, IntPtr.Zero, CDS_RESET | CDS_UPDATEREGISTRY, IntPtr.Zero);
Console.WriteLine("result = " + res.ToString());
If the monitor is already enabled, this changes the resolution successfully. But if the monitor isn't attached to the desktop already, this won't activate it. So does anyone have a code example that works?
To enable a monitor, set its position to something other than 0,0, like as shown:
POINTL enabledPosition = new POINTL();
enabledPosition.x = -1280;
enabledPosition.y = 0;
dm.dmPosition = enabledPosition;
dm.dmFields = DM.Position;
res = ChangeDisplaySettingsEx(d.DeviceName, ref dm, IntPtr.Zero, (uint) DeviceFlags.CDS_UPDATEREGISTRY, IntPtr.Zero);