问题
I would like to create an console application which has some events registered. The problem is that those events are never fired. In this particular situation in a Windows Forms application I should invoke Application.Run(new Form());
after registering those events, but it is Console Application
.
So I would like to create an event loop or message pipe if it is possible for that console application.
The application:
using Bonjour;
using System;
using System.Threading;
using Bonjour;
using System;
using System.Threading;
namespace ConsoleApplication4 {
class Program {
[STAThread]
static void Main() {
DNSSDService service = new DNSSDService();
DNSSDEventManager eventManager = new DNSSDEventManager();
eventManager.ServiceFound += new _IDNSSDEvents_ServiceFoundEventHandler(eventManager_ServiceFound);
DNSSDService browse = service.Browse(0, 0, "_axis-video._tcp", null, eventManager);
Console.ReadLine();
}
static void eventManager_ServiceFound(DNSSDService browser, DNSSDFlags flags, uint ifIndex, string serviceName, string regtype, string domain) {
Console.WriteLine("browser: " + browser + "\nDNSSDFlags " + flags + "\nifIndex " + ifIndex + "\nserviceName: " + serviceName + "\nregtype: " + regtype + "\ndomain: " + domain);
DNSSDEventManager eventManager1 = new DNSSDEventManager();
eventManager1.ServiceResolved += new _IDNSSDEvents_ServiceResolvedEventHandler(eventManager_ServiceResolved);
browser.Resolve(flags, ifIndex, serviceName, regtype, domain, eventManager1);
}
private static void eventManager_ServiceResolved(DNSSDService service, DNSSDFlags flags, uint ifIndex, string fullname, string hostname, ushort port, TXTRecord record) {
Console.WriteLine("----------------------------------------");
Console.WriteLine("FFFFFFFFFFFFFFFFFFFFFOUUUUUUUUUUUUUUUUND");
Console.WriteLine("DNSSDService " + service + "\nDNSSDFlags " + flags + "\nifindex " + ifIndex + "\nfullname " + fullname + "hostname " + hostname + "\nport " + port + "\nrecord " + record);
string s = record.ToString();
Console.WriteLine("mac " + record.GetValueForKey("macaddress"));
var str = System.Text.Encoding.Default.GetString(record.GetValueForKey("macaddress"));
Console.WriteLine("mac " + str);
Console.WriteLine("----------------------------------------");
DNSSDEventManager eventManager = new DNSSDEventManager();
eventManager.AddressFound += new _IDNSSDEvents_AddressFoundEventHandler(eventManager_AddressFound);
DNSSDAddressFamily family = new DNSSDAddressFamily();
service.GetAddrInfo(flags, ifIndex, family, hostname, eventManager);
}
private static void eventManager_AddressFound(DNSSDService service, DNSSDFlags flags, uint ifIndex, string hostname, DNSSDAddressFamily addressFamily, string address, uint ttl) {
Console.WriteLine("----------------------------------------");
Console.WriteLine(hostname);
Console.WriteLine(address);
Console.WriteLine("----------------------------------------");
}
}
}
enter code here
WPF Main0Window.xaml.cs
namespace HomeSecurity {
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window {
public MainWindow() {
InitializeComponent();
}
private void Window_Loaded(object sender, RoutedEventArgs e) {
createGUI();
}
private void createGUI() {
Scanner.ScanService();
//THIS CODE WON'T RUN BECAUSE Scanner.ScanService(); have frozen it
AddVideoStream("192.168.0.2");
AddVideoStream("192.168.0.2");
AddVideoStream("192.168.0.2");
}
//TEN
private void AddVideoStream(String sourceIP) {
int cols = 2;
int formsHostWidth = (int)(VideoPanel.ActualWidth / cols) - 4;
WindowsFormsHost formsHost = new WindowsFormsHost();
VideoStream videoStream = new VideoStream(sourceIP);
formsHost.Width = formsHostWidth;
formsHost.Height = videoStream.GetPrefferedHeight(formsHostWidth);
formsHost.Child = videoStream;
Border lineBorder = new Border();
lineBorder.BorderBrush = Brushes.Green;
lineBorder.BorderThickness = new Thickness(2);
lineBorder.Child = formsHost;
VideoPanel.Children.Add(lineBorder);
}
}
}
And the class Scanner which has method ScanService:
using Bonjour;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace HomeSecurity {
class Scanner {
[STAThread]
public static void ScanService() {
try {
DNSSDService service = new DNSSDService();
DNSSDEventManager eventManager = new DNSSDEventManager();
eventManager.ServiceFound += new _IDNSSDEvents_ServiceFoundEventHandler(eventManager_ServiceFound);
DNSSDService browse = service.Browse(0, 0, "_axis-video._tcp", null, eventManager);
System.Windows.Threading.Dispatcher.Run(); // BLOCKS EVERYTHING
} catch (Exception e) {
Console.WriteLine("--------------------EXCEPTION-----------------");
Console.WriteLine(e);
Console.WriteLine("--------------------EXCEPTION-----------------");
}
}
static void eventManager_ServiceFound(DNSSDService browser, DNSSDFlags flags, uint ifIndex, string serviceName, string regtype, string domain) {
try {
Console.WriteLine("---------------- eventManager_ServiceFound------------------------");
Console.WriteLine("browser: " + browser + "\nDNSSDFlags " + flags + "\nifIndex " + ifIndex + "\nserviceName: " + serviceName + "\nregtype: " + regtype + "\ndomain: " + domain);
Console.WriteLine("----------------------------------------");
// DNSSDService service2 = new DNSSDService();
DNSSDEventManager eventManager1 = new DNSSDEventManager();
eventManager1.ServiceResolved += new _IDNSSDEvents_ServiceResolvedEventHandler(eventManager_ServiceResolved);
browser.Resolve(flags, ifIndex, serviceName, regtype, domain, eventManager1);
}
catch (Exception e) {
Console.WriteLine("--------------------EXCEPTION--------eventManager_ServiceFound---------");
Console.WriteLine(e);
Console.WriteLine("--------------------EXCEPTION-----------------");
}
}
private static void eventManager_ServiceResolved(DNSSDService service, DNSSDFlags flags, uint ifIndex, string fullname, string hostname, ushort port, TXTRecord record) {
try {
Console.WriteLine("-------------------eventManager_ServiceResolved---------------------");
Console.WriteLine("DNSSDService " + service + "\nDNSSDFlags " + flags + "\nifindex " + ifIndex + "\nfullname " + fullname + "hostname " + hostname + "\nport " + port + "\nrecord " + record);
var str = System.Text.Encoding.Default.GetString(record.GetValueForKey("macaddress"));
Console.WriteLine("mac " + str);
Console.WriteLine("----------------------------------------");
// DNSSDService service2 = new DNSSDService();
DNSSDEventManager eventManager = new DNSSDEventManager();
eventManager.AddressFound += new _IDNSSDEvents_AddressFoundEventHandler(eventManager_AddressFound);
DNSSDAddressFamily family = new DNSSDAddressFamily();
service.GetAddrInfo(flags, ifIndex, family, hostname, eventManager);
}
catch (Exception e) {
Console.WriteLine("--------------------EXCEPTION--------eventManager_ServiceResolved---------");
Console.WriteLine(e);
Console.WriteLine("--------------------EXCEPTION-----------------");
}
}
private static void eventManager_AddressFound(DNSSDService service, DNSSDFlags flags, uint ifIndex, string hostname, DNSSDAddressFamily addressFamily, string address, uint ttl) {
try {
Console.WriteLine("------------------eventManager_AddressFound----------------------");
Console.WriteLine("hosname " + hostname);
Console.WriteLine("address " + address);
Console.WriteLine("----------------------------------------");
}
catch (Exception e) {
Console.WriteLine("--------------------EXCEPTION-----eventManager_AddressFound------------");
Console.WriteLine(e);
Console.WriteLine("--------------------EXCEPTION-----------------");
}
}
}
}
回答1:
I don't understand what your code sample has to do with events or Windows Forms. It looks like you're just using the ZeroConf .NET libraries, which doesn't appear to have anything to with Windows forms or event loops?
At any rate, if you want to create (and pump) a windows message loop on a background thread, you can do it using the WPF dispatcher class - call System.Windows.Threading.Dispatcher.Run()
from a background thread.
If you don't want to use WPF, you can use Windows Forms - just call Application.Run(someForm);
, but create someForm
as a 0-width 0-height hidden window and set ShowInTaskBar = false
on it, and that will create (and pump) a windows message loop too.
It looks like the ZeroConf libraries are COM objects, and the library is trying to post back to your main thread as it's using the STA (Single-Threaded-Apartment) threading model.
Here's a guess. If the COM objects are set up to use 'Both' as their threading model, then they'll pick STA, because you're creating the objects on your main thread, and by default the main thread of a .NET app is STAThread
.
Try deleting the [STAThread] attribute from your Main
method, and putting an [MTAThread]
attribute instead. If the ZeroConf COM objects are using 'Both' rather than STA as their threading model, then because you create them from an MTA thread, the events will be posted to arbitrary threadpool worker threads, instead of posted back to your main STA thread.
If this is the case, this should solve it and you won't need to run a message loop at all. You'll have to write thread-safe code when processing your events though.
回答2:
I run event loop manually (instead of Console.ReadLine(); ) in the Main in such cases.
public class User32Wrapper
{
// GetMessage
[DllImport(@"user32.dll", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)]
public static extern bool GetMessage(ref MSG message, IntPtr hWnd, uint wMsgFilterMin, uint wMsgFilterMax);
[DllImport(@"user32.dll", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)]
public static extern bool TranslateMessage(ref MSG message);
[DllImport(@"user32.dll", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)]
public static extern long DispatchMessage(ref MSG message);
[StructLayout(LayoutKind.Sequential)]
public struct POINT
{
long x;
long y;
}
[StructLayout(LayoutKind.Sequential)]
public struct MSG
{
IntPtr hwnd;
public uint message;
UIntPtr wParam;
IntPtr lParam;
uint time;
POINT pt;
}
}
private const int WM_CUSTOM_EXIT = 0x0400 + 2000;
private const int WM_CUSTOM_1 = 0x0400 + 2001;
private const int WM_CUSTOM_2 = 0x0400 + 2002;
private const int WM_CUSTOM_3 = 0x0400 + 2003;
static void Main(string[] args)
{
ILog log = LogManager.GetLogger("Main");
// running working threads
...
// Main message loop
User32Wrapper.MSG msg = new User32Wrapper.MSG();
while (User32Wrapper.GetMessage(ref msg, IntPtr.Zero, 0, 0))
{
if (WM_CUSTOM_EXIT == msg.message) // add WM_CLOSE if you need
{
break;
}
if (WM_CUSTOM_1 == msg.message)
{
onWmCustom1();
}
//if (check_any_message_you_need == msg.message)
// you can for example check if Enter has been pressed to emulate Console.Readline();
User32Wrapper.TranslateMessage(ref msg);
User32Wrapper.DispatchMessage(ref msg);
}
log.Info("Finished");
}
In the sample above console application will work untill (WM_USER+2000) message is received by main thread. So in another application (or in other thread in the same app) you need do like this:
const int WM_CUSTOM_EXIT = WM_USER + 2000;
PROCESS_INFORMATION* pInfo = ...;
::PostThreadMessageA(pInfo->dwThreadId, WM_CUSTOM_EXIT, 0, 0)
回答3:
If you are using external components, specifically if any of those components is COM, you really should be pumping, i.e. having a standard message loop running on your thread. Else things could get very ugly, but the details of it are awful and hidden in how .NET and COM work behind the scenes -- I can't say I understand that myself.
But, leaving all that uncertainty aside, have you tried creating a simple Sleep loop and avoid using hanging operations such as any Console.Read calls. Something like:
while( some condition )
{
// Check if user pressed a key
while (Console.KeyAvailable)
{
// TODO: Read key
}
// Do some other processing maybe?
// Sleep for 100 ms as to avoid spinning at 100% cpu
System.Threading.Thread.Sleep(100);
}
Do the events come through then?
来源:https://stackoverflow.com/questions/21103669/how-do-i-create-an-event-loop-message-pipe-in-a-console-application