Initial commit: Wonderware / System Platform tools and reference
Five tools under one repo, all docs organized per DOCS-GUIDE.md: - aalogcli: .NET 4.8 / x86 CliFx CLI for reading System Platform binary logs (*.aaLGX) for LLM debugging, built on aaOpenSource/aaLog. Commands: last, tail, range, unread, fields. Stable JSON envelope under --llm-json. Build template under lib/build/ for rebuilding aaLogReader.dll. - aot: ArchestrA Object Toolkit 2014 v4.0 reference material. Dev guide (Markdown converted from CHM), API reference for the ArchestrA.Toolkit namespace, and the Monitor / Watchdog VS sample solutions. - graccesscli: .NET 4.8 / x86 CliFx CLI that automates Galaxy configuration via the ArchestrA GRAccess COM interop. Includes session daemon, IPC protocol, and llm-json envelope contract. - grdb: SQL/DDL exploration of the Galaxy Repository database. DDL captures, reusable queries, hierarchy / contained-name <-> tag-name translation notes. - histdb: LLM-oriented reference for AVEVA Historian retrieval. INSQL linked-server, extension tables, every wwXxx time-domain extension, every retrieval mode, alarm/event SQL recipes, REST API. Distilled from the 243-page Historian Retrieval Guide. Root contains: - CLAUDE.md: thin index pointing into each tool's README. - DOCS-GUIDE.md: doctrine for organizing docs for LLM consumption. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,62 @@
|
||||
using System;
|
||||
using System.IO.Pipes;
|
||||
using System.Threading.Tasks;
|
||||
using ZB.MOM.WW.GRAccess.Cli.Protocol;
|
||||
|
||||
namespace ZB.MOM.WW.GRAccess.Cli.Session
|
||||
{
|
||||
public sealed class SessionClient : IDisposable
|
||||
{
|
||||
private readonly string _pipeName;
|
||||
|
||||
private SessionClient(string pipeName)
|
||||
{
|
||||
_pipeName = pipeName;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Try to connect to a running session for the given galaxy.
|
||||
/// Returns false if no session is available.
|
||||
/// </summary>
|
||||
public static bool TryConnect(string galaxyName, out SessionClient client)
|
||||
{
|
||||
client = null;
|
||||
|
||||
var info = SessionInfo.Load(galaxyName);
|
||||
if (info == null || !info.IsAlive())
|
||||
{
|
||||
// Clean up stale file
|
||||
info?.Delete();
|
||||
return false;
|
||||
}
|
||||
|
||||
client = new SessionClient(info.PipeName);
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task<PipeResponse> SendCommandAsync(PipeRequest request)
|
||||
{
|
||||
using (var pipe = new NamedPipeClientStream(".", _pipeName, PipeDirection.InOut))
|
||||
{
|
||||
pipe.Connect(5000);
|
||||
await PipeProtocol.WriteMessageAsync(pipe, request).ConfigureAwait(false);
|
||||
return await PipeProtocol.ReadMessageAsync<PipeResponse>(pipe).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
public Task<PipeResponse> SendShutdownAsync()
|
||||
{
|
||||
return SendCommandAsync(PipeRequest.Shutdown());
|
||||
}
|
||||
|
||||
public Task<PipeResponse> SendStatusAsync()
|
||||
{
|
||||
return SendCommandAsync(PipeRequest.Status());
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// No persistent resources to clean up — each call opens/closes its own pipe
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,302 @@
|
||||
using System;
|
||||
using System.IO.Pipes;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Newtonsoft.Json;
|
||||
using Serilog;
|
||||
using ZB.MOM.WW.GRAccess.Cli.GRAccess;
|
||||
using ZB.MOM.WW.GRAccess.Cli.Protocol;
|
||||
|
||||
namespace ZB.MOM.WW.GRAccess.Cli.Session
|
||||
{
|
||||
public sealed class SessionDaemon : IDisposable
|
||||
{
|
||||
private static readonly ILogger Log = Serilog.Log.ForContext<SessionDaemon>();
|
||||
|
||||
private readonly string _galaxyName;
|
||||
private readonly string _nodeName;
|
||||
private readonly string _pipeName;
|
||||
private readonly TimeSpan _idleTimeout;
|
||||
private readonly CancellationTokenSource _shutdownCts = new CancellationTokenSource();
|
||||
|
||||
private StaComThread _staThread;
|
||||
private GRAccessConnection _connection;
|
||||
private Mutex _instanceMutex;
|
||||
private DateTime _lastActivity;
|
||||
private Timer _idleTimer;
|
||||
|
||||
public SessionDaemon(string galaxyName, string nodeName, TimeSpan idleTimeout)
|
||||
{
|
||||
_galaxyName = galaxyName;
|
||||
_nodeName = nodeName;
|
||||
_pipeName = PipeProtocol.GetPipeName(galaxyName);
|
||||
_idleTimeout = idleTimeout;
|
||||
_lastActivity = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Static entry point called from Program.Main when --daemon flag is present.
|
||||
/// </summary>
|
||||
public static int Run(string[] args)
|
||||
{
|
||||
string galaxyName = null;
|
||||
string nodeName = null;
|
||||
int idleTimeoutMinutes = 30;
|
||||
|
||||
for (int i = 1; i < args.Length; i++)
|
||||
{
|
||||
switch (args[i])
|
||||
{
|
||||
case "--galaxy" when i + 1 < args.Length:
|
||||
galaxyName = args[++i];
|
||||
break;
|
||||
case "--node" when i + 1 < args.Length:
|
||||
nodeName = args[++i];
|
||||
break;
|
||||
case "--idle-timeout" when i + 1 < args.Length:
|
||||
int.TryParse(args[++i], out idleTimeoutMinutes);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(galaxyName) || string.IsNullOrEmpty(nodeName))
|
||||
{
|
||||
Console.Error.WriteLine("--galaxy and --node are required for daemon mode.");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Configure daemon logging
|
||||
Serilog.Log.Logger = new LoggerConfiguration()
|
||||
.WriteTo.File(
|
||||
System.IO.Path.Combine(
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
|
||||
"ZB.MOM.WW.GRAccess.Cli", "logs", $"daemon-{galaxyName.ToLowerInvariant()}.log"),
|
||||
rollingInterval: RollingInterval.Day,
|
||||
retainedFileCountLimit: 7)
|
||||
.CreateLogger();
|
||||
|
||||
try
|
||||
{
|
||||
using (var daemon = new SessionDaemon(galaxyName, nodeName,
|
||||
TimeSpan.FromMinutes(idleTimeoutMinutes)))
|
||||
{
|
||||
return daemon.Start();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Fatal(ex, "Daemon terminated unexpectedly");
|
||||
return 1;
|
||||
}
|
||||
finally
|
||||
{
|
||||
Serilog.Log.CloseAndFlush();
|
||||
}
|
||||
}
|
||||
|
||||
public int Start()
|
||||
{
|
||||
// 1. Acquire mutex
|
||||
var mutexName = $"Global\\graccess-session-{_galaxyName.ToLowerInvariant()}";
|
||||
_instanceMutex = new Mutex(true, mutexName, out bool createdNew);
|
||||
if (!createdNew)
|
||||
{
|
||||
Log.Warning("Another daemon is already running for galaxy {Galaxy}", _galaxyName);
|
||||
return 1;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// 2. Start STA thread
|
||||
_staThread = new StaComThread();
|
||||
_staThread.Start();
|
||||
|
||||
// 3. Connect to galaxy on STA thread
|
||||
_connection = new GRAccessConnection(_galaxyName, _nodeName);
|
||||
_staThread.RunAsync(() => _connection.Connect()).GetAwaiter().GetResult();
|
||||
|
||||
// 4. Write session info
|
||||
var sessionInfo = new SessionInfo
|
||||
{
|
||||
GalaxyName = _galaxyName,
|
||||
NodeName = _nodeName,
|
||||
PipeName = _pipeName,
|
||||
ProcessId = System.Diagnostics.Process.GetCurrentProcess().Id,
|
||||
StartedAtUtc = DateTime.UtcNow
|
||||
};
|
||||
sessionInfo.Save();
|
||||
|
||||
Log.Information("Daemon started for galaxy {Galaxy} on pipe {Pipe}", _galaxyName, _pipeName);
|
||||
|
||||
// 5. Start idle timer
|
||||
_idleTimer = new Timer(_ => CheckIdleTimeout(), null,
|
||||
TimeSpan.FromSeconds(30), TimeSpan.FromSeconds(30));
|
||||
|
||||
// 6. Accept connections (blocks until shutdown)
|
||||
AcceptConnectionsAsync(_shutdownCts.Token).GetAwaiter().GetResult();
|
||||
|
||||
return 0;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Serilog.Log.Error(ex, "Daemon startup failed for galaxy {Galaxy} on node {Node}", _galaxyName, _nodeName);
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
// 7. Cleanup
|
||||
_idleTimer?.Dispose();
|
||||
|
||||
if (_connection != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
_staThread.RunAsync(() => _connection.Disconnect()).GetAwaiter().GetResult();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Warning(ex, "Error disconnecting during shutdown");
|
||||
}
|
||||
}
|
||||
|
||||
_staThread?.Dispose();
|
||||
|
||||
var info = SessionInfo.Load(_galaxyName);
|
||||
info?.Delete();
|
||||
|
||||
_instanceMutex.ReleaseMutex();
|
||||
_instanceMutex.Dispose();
|
||||
|
||||
Log.Information("Daemon stopped for galaxy {Galaxy}", _galaxyName);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task AcceptConnectionsAsync(CancellationToken ct)
|
||||
{
|
||||
while (!ct.IsCancellationRequested)
|
||||
{
|
||||
try
|
||||
{
|
||||
using (var server = new NamedPipeServerStream(
|
||||
_pipeName,
|
||||
PipeDirection.InOut,
|
||||
NamedPipeServerStream.MaxAllowedServerInstances,
|
||||
PipeTransmissionMode.Byte,
|
||||
PipeOptions.Asynchronous))
|
||||
{
|
||||
var waitTask = Task.Factory.FromAsync(
|
||||
server.BeginWaitForConnection, server.EndWaitForConnection, null);
|
||||
|
||||
// Wait for connection or cancellation
|
||||
var completedTask = await Task.WhenAny(waitTask, Task.Delay(-1, ct))
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if (ct.IsCancellationRequested)
|
||||
break;
|
||||
|
||||
await waitTask.ConfigureAwait(false);
|
||||
|
||||
_lastActivity = DateTime.UtcNow;
|
||||
await HandleClientAsync(server).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
break;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Error accepting pipe connection");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task HandleClientAsync(NamedPipeServerStream server)
|
||||
{
|
||||
try
|
||||
{
|
||||
var request = await PipeProtocol.ReadMessageAsync<PipeRequest>(server).ConfigureAwait(false);
|
||||
if (request == null)
|
||||
return;
|
||||
|
||||
PipeResponse response;
|
||||
|
||||
switch (request.Type)
|
||||
{
|
||||
case "shutdown":
|
||||
response = PipeResponse.Ok("Shutting down");
|
||||
await PipeProtocol.WriteMessageAsync(server, response).ConfigureAwait(false);
|
||||
_shutdownCts.Cancel();
|
||||
return;
|
||||
|
||||
case "status":
|
||||
var status = new
|
||||
{
|
||||
galaxy = _galaxyName,
|
||||
node = _nodeName,
|
||||
connected = _connection?.IsConnected ?? false,
|
||||
uptime = (DateTime.UtcNow - _lastActivity).ToString()
|
||||
};
|
||||
response = PipeResponse.Ok(JsonConvert.SerializeObject(status));
|
||||
break;
|
||||
|
||||
case "execute":
|
||||
response = await ExecuteCommandAsync(request).ConfigureAwait(false);
|
||||
break;
|
||||
|
||||
default:
|
||||
response = PipeResponse.Fail($"Unknown request type: {request.Type}");
|
||||
break;
|
||||
}
|
||||
|
||||
await PipeProtocol.WriteMessageAsync(server, response).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Error handling client request");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<PipeResponse> ExecuteCommandAsync(PipeRequest request)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await _staThread.RunAsync<PipeResponse>(() =>
|
||||
{
|
||||
var output = GRAccessCommandDispatcher.Execute(
|
||||
_connection.Galaxy,
|
||||
request.Command,
|
||||
request.Subcommand,
|
||||
request.Args);
|
||||
|
||||
return PipeResponse.Ok(output);
|
||||
}).ConfigureAwait(false);
|
||||
}
|
||||
catch (NotSupportedException ex)
|
||||
{
|
||||
return PipeResponse.Fail(ex.Message, 2);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return PipeResponse.Fail($"Execution error: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private void CheckIdleTimeout()
|
||||
{
|
||||
if (DateTime.UtcNow - _lastActivity > _idleTimeout)
|
||||
{
|
||||
Log.Information("Idle timeout reached ({Timeout} min), shutting down",
|
||||
_idleTimeout.TotalMinutes);
|
||||
_shutdownCts.Cancel();
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_shutdownCts?.Cancel();
|
||||
_idleTimer?.Dispose();
|
||||
_shutdownCts?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace ZB.MOM.WW.GRAccess.Cli.Session
|
||||
{
|
||||
public class SessionInfo
|
||||
{
|
||||
[JsonProperty("galaxyName")]
|
||||
public string GalaxyName { get; set; }
|
||||
|
||||
[JsonProperty("nodeName")]
|
||||
public string NodeName { get; set; }
|
||||
|
||||
[JsonProperty("pipeName")]
|
||||
public string PipeName { get; set; }
|
||||
|
||||
[JsonProperty("processId")]
|
||||
public int ProcessId { get; set; }
|
||||
|
||||
[JsonProperty("startedAtUtc")]
|
||||
public DateTime StartedAtUtc { get; set; }
|
||||
|
||||
private static string SessionsDir =>
|
||||
Path.Combine(
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
|
||||
"ZB.MOM.WW.GRAccess.Cli",
|
||||
"sessions");
|
||||
|
||||
public static string GetSessionFilePath(string galaxyName)
|
||||
{
|
||||
return Path.Combine(SessionsDir, galaxyName.ToLowerInvariant() + ".json");
|
||||
}
|
||||
|
||||
public static SessionInfo Load(string galaxyName)
|
||||
{
|
||||
var path = GetSessionFilePath(galaxyName);
|
||||
if (!File.Exists(path))
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
var json = File.ReadAllText(path);
|
||||
return JsonConvert.DeserializeObject<SessionInfo>(json);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public void Save()
|
||||
{
|
||||
var path = GetSessionFilePath(GalaxyName);
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(path));
|
||||
var json = JsonConvert.SerializeObject(this, Formatting.Indented);
|
||||
File.WriteAllText(path, json);
|
||||
}
|
||||
|
||||
public void Delete()
|
||||
{
|
||||
var path = GetSessionFilePath(GalaxyName);
|
||||
if (File.Exists(path))
|
||||
File.Delete(path);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if the daemon process is still running.
|
||||
/// </summary>
|
||||
public bool IsAlive()
|
||||
{
|
||||
try
|
||||
{
|
||||
var process = Process.GetProcessById(ProcessId);
|
||||
return !process.HasExited;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,215 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Serilog;
|
||||
|
||||
namespace ZB.MOM.WW.GRAccess.Cli.Session
|
||||
{
|
||||
/// <summary>
|
||||
/// Dedicated STA thread with a raw Win32 message pump for COM interop.
|
||||
/// All GRAccess COM objects must be created and called on this thread.
|
||||
/// </summary>
|
||||
public sealed class StaComThread : IDisposable
|
||||
{
|
||||
private const uint WM_APP = 0x8000;
|
||||
private const uint PM_NOREMOVE = 0x0000;
|
||||
|
||||
private static readonly ILogger Log = Serilog.Log.ForContext<StaComThread>();
|
||||
|
||||
private readonly Thread _thread;
|
||||
private readonly TaskCompletionSource<bool> _ready = new TaskCompletionSource<bool>();
|
||||
private readonly ConcurrentQueue<Action> _workItems = new ConcurrentQueue<Action>();
|
||||
private volatile uint _nativeThreadId;
|
||||
private bool _disposed;
|
||||
|
||||
public StaComThread()
|
||||
{
|
||||
_thread = new Thread(ThreadEntry)
|
||||
{
|
||||
Name = "GRAccess-STA",
|
||||
IsBackground = true
|
||||
};
|
||||
_thread.SetApartmentState(ApartmentState.STA);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Starts the STA thread and waits until the message pump is running.
|
||||
/// </summary>
|
||||
public void Start()
|
||||
{
|
||||
_thread.Start();
|
||||
_ready.Task.GetAwaiter().GetResult();
|
||||
Log.Information("STA COM thread started (ThreadId={ThreadId})", _thread.ManagedThreadId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Marshals a synchronous action onto the STA thread and returns a Task
|
||||
/// that completes when the action finishes.
|
||||
/// </summary>
|
||||
public Task RunAsync(Action action)
|
||||
{
|
||||
if (_disposed) throw new ObjectDisposedException(nameof(StaComThread));
|
||||
|
||||
var tcs = new TaskCompletionSource<bool>();
|
||||
_workItems.Enqueue(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
action();
|
||||
tcs.TrySetResult(true);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
tcs.TrySetException(ex);
|
||||
}
|
||||
});
|
||||
PostThreadMessage(_nativeThreadId, WM_APP, IntPtr.Zero, IntPtr.Zero);
|
||||
return tcs.Task;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Marshals a synchronous function onto the STA thread and returns
|
||||
/// a Task<T> with the result.
|
||||
/// </summary>
|
||||
public Task<T> RunAsync<T>(Func<T> func)
|
||||
{
|
||||
if (_disposed) throw new ObjectDisposedException(nameof(StaComThread));
|
||||
|
||||
var tcs = new TaskCompletionSource<T>();
|
||||
_workItems.Enqueue(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
tcs.TrySetResult(func());
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
tcs.TrySetException(ex);
|
||||
}
|
||||
});
|
||||
PostThreadMessage(_nativeThreadId, WM_APP, IntPtr.Zero, IntPtr.Zero);
|
||||
return tcs.Task;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed) return;
|
||||
_disposed = true;
|
||||
|
||||
try
|
||||
{
|
||||
if (_nativeThreadId != 0)
|
||||
PostThreadMessage(_nativeThreadId, WM_APP + 1, IntPtr.Zero, IntPtr.Zero);
|
||||
_thread.Join(TimeSpan.FromSeconds(5));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Warning(ex, "Error shutting down STA COM thread");
|
||||
}
|
||||
|
||||
Log.Information("STA COM thread stopped");
|
||||
}
|
||||
|
||||
private void ThreadEntry()
|
||||
{
|
||||
try
|
||||
{
|
||||
_nativeThreadId = GetCurrentThreadId();
|
||||
|
||||
// Force message queue creation by peeking
|
||||
MSG msg;
|
||||
PeekMessage(out msg, IntPtr.Zero, 0, 0, PM_NOREMOVE);
|
||||
|
||||
_ready.TrySetResult(true);
|
||||
|
||||
// Run the message loop — blocks until WM_QUIT
|
||||
while (GetMessage(out msg, IntPtr.Zero, 0, 0) > 0)
|
||||
{
|
||||
if (msg.message == WM_APP)
|
||||
{
|
||||
DrainQueue();
|
||||
}
|
||||
else if (msg.message == WM_APP + 1)
|
||||
{
|
||||
// Shutdown signal — drain remaining work then quit
|
||||
DrainQueue();
|
||||
PostQuitMessage(0);
|
||||
}
|
||||
else
|
||||
{
|
||||
TranslateMessage(ref msg);
|
||||
DispatchMessage(ref msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "STA COM thread crashed");
|
||||
_ready.TrySetException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrainQueue()
|
||||
{
|
||||
while (_workItems.TryDequeue(out var workItem))
|
||||
{
|
||||
try
|
||||
{
|
||||
workItem();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Unhandled exception in STA work item");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#region Win32 PInvoke
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct MSG
|
||||
{
|
||||
public IntPtr hwnd;
|
||||
public uint message;
|
||||
public IntPtr wParam;
|
||||
public IntPtr lParam;
|
||||
public uint time;
|
||||
public POINT pt;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct POINT
|
||||
{
|
||||
public int x;
|
||||
public int y;
|
||||
}
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern int GetMessage(out MSG lpMsg, IntPtr hWnd, uint wMsgFilterMin, uint wMsgFilterMax);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
private static extern bool TranslateMessage(ref MSG lpMsg);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern IntPtr DispatchMessage(ref MSG lpMsg);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
private static extern bool PostThreadMessage(uint idThread, uint Msg, IntPtr wParam, IntPtr lParam);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern void PostQuitMessage(int nExitCode);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
private static extern bool PeekMessage(out MSG lpMsg, IntPtr hWnd, uint wMsgFilterMin, uint wMsgFilterMax, uint wRemoveMsg);
|
||||
|
||||
[DllImport("kernel32.dll")]
|
||||
private static extern uint GetCurrentThreadId();
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user