C# SCADA
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

620 lines
27 KiB

using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
namespace DDEDriver
{
public abstract class DdemlServer : IDisposable
{
private int _InstanceId; // DDEML instance identifier
private string _Service; // DDEML service name
private IntPtr _ServiceHandle = IntPtr.Zero; // DDEML service handle
private bool _Disposed = false;
private HashSet<IntPtr> _ConversationTable = new HashSet<IntPtr>();
private DdeCallback _Callback;
public DdemlServer()
{
_Callback = OnDdeCallback;
}
~DdemlServer()
{
Dispose(false);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected void Dispose(bool disposing)
{
if (!_Disposed)
{
_Disposed = true;
if (IsRegistered)
{
// Unregister the service name.
Unregister();
if (disposing)
{
_ServiceHandle = IntPtr.Zero;
_InstanceId = 0;
}
}
}
}
public string Service
{
get { return _Service; }
}
public bool IsRegistered
{
get { return _ServiceHandle != IntPtr.Zero; }
}
internal bool IsDisposed
{
get { return _Disposed; }
}
public int Initialize(int afCmd)
{
int instanceId = 0;
Ddeml.DdeInitialize(ref instanceId, _Callback, afCmd, 0);
return instanceId;
}
public void Uninitialize()
{
Ddeml.DdeUninitialize(_InstanceId);
}
public string Register(string service)
{
if (IsRegistered || _InstanceId != 0)
{
throw new InvalidOperationException("AlreadyRegisteredMessage");
}
if (service == null || service.Length > Ddeml.MAX_STRING_SIZE)
{
throw new ArgumentNullException("service");
}
_Service = service;
_ConversationTable.Clear();
_InstanceId = Initialize(Ddeml.APPCLASS_STANDARD);
_ServiceHandle = Ddeml.DdeCreateStringHandle(_InstanceId, _Service, Ddeml.CP_WINANSI);
// Register the service name.
if (Ddeml.DdeNameService(_InstanceId, _ServiceHandle, IntPtr.Zero, Ddeml.DNS_REGISTER) == IntPtr.Zero)
{
Ddeml.DdeFreeStringHandle(_InstanceId, _ServiceHandle);
_ServiceHandle = IntPtr.Zero;
}
// If the service handle is null then the service name could not be registered.
if (_ServiceHandle == IntPtr.Zero)
{
int error = Ddeml.DdeGetLastError(_InstanceId);
return Ddeml.DDEGetErrorMsg(error);
}
return null;
}
public void Unregister()
{
Ddeml.DdeNameService(_InstanceId, _ServiceHandle, IntPtr.Zero, Ddeml.DNS_UNREGISTER);
// Free the service string handle.
Ddeml.DdeFreeStringHandle(_InstanceId, _ServiceHandle);
// Indicate that the service name is no longer registered.
_ServiceHandle = IntPtr.Zero;
_InstanceId = 0;
}
private IntPtr OnDdeCallback(int uType, ConversionFormat uFmt, IntPtr hConv, IntPtr hsz1, IntPtr hsz2, IntPtr hData, uint dwData1, uint dwData2)
{
// Create a new transaction object that will be dispatched to a DdemlClient, DdemlServer, or ITransactionFilter.
// Dispatch the transaction.
switch (uType)
{
case Ddeml.XTYP_MONITOR:
switch (dwData2)
{
case Ddeml.MF_CALLBACKS:
{
// Get the MONCBSTRUCT object.
int length = 0;
IntPtr phData = Ddeml.DdeAccessData(hData, ref length);
MONCBSTRUCT mon = (MONCBSTRUCT)Marshal.PtrToStructure(phData, typeof(MONCBSTRUCT));
Ddeml.DdeUnaccessData(hData);
OnCallback(mon);
return IntPtr.Zero;
}
case Ddeml.MF_CONV:
{
// Get the MONCONVSTRUCT object.
int length = 0;
IntPtr phData = Ddeml.DdeAccessData(hData, ref length);
MONCONVSTRUCT mon = (MONCONVSTRUCT)Marshal.PtrToStructure(phData, typeof(MONCONVSTRUCT));
Ddeml.DdeUnaccessData(hData);
OnConversation(mon);
return IntPtr.Zero;
}
case Ddeml.MF_ERRORS:
{
// Get the MONERRSTRUCT object.
int length = 0;
IntPtr phData = Ddeml.DdeAccessData(hData, ref length);
MONERRSTRUCT mon = (MONERRSTRUCT)Marshal.PtrToStructure(phData, typeof(MONERRSTRUCT));
Ddeml.DdeUnaccessData(hData);
OnError(mon);
return IntPtr.Zero;
}
case Ddeml.MF_HSZ_INFO:
{
// Get the MONHSZSTRUCT object.
int length = 0;
IntPtr phData = Ddeml.DdeAccessData(hData, ref length);
MONHSZSTRUCT mon = (MONHSZSTRUCT)Marshal.PtrToStructure(phData, typeof(MONHSZSTRUCT));
Ddeml.DdeUnaccessData(hData);
OnString(mon);
return IntPtr.Zero;
}
case Ddeml.MF_LINKS:
{
// Get the MONLINKSTRUCT object.
int length = 0;
IntPtr phData = Ddeml.DdeAccessData(hData, ref length);
MONLINKSTRUCT mon = (MONLINKSTRUCT)Marshal.PtrToStructure(phData, typeof(MONLINKSTRUCT));
Ddeml.DdeUnaccessData(hData);
OnLink(mon);
return IntPtr.Zero;
}
case Ddeml.MF_POSTMSGS:
{
// Get the MONMSGSTRUCT object.
int length = 0;
IntPtr phData = Ddeml.DdeAccessData(hData, ref length);
MONMSGSTRUCT mon = (MONMSGSTRUCT)Marshal.PtrToStructure(phData, typeof(MONMSGSTRUCT));
Ddeml.DdeUnaccessData(hData);
OnPost(mon);
return IntPtr.Zero;
}
case Ddeml.MF_SENDMSGS:
{
// Get the MONMSGSTRUCT object.
int length = 0;
IntPtr phData = Ddeml.DdeAccessData(hData, ref length);
MONMSGSTRUCT mon = (MONMSGSTRUCT)Marshal.PtrToStructure(phData, typeof(MONMSGSTRUCT));
Ddeml.DdeUnaccessData(hData);
OnSend(mon);
return IntPtr.Zero;
}
}
break;
case Ddeml.XTYP_ADVDATA:
unsafe
{
sbyte* pSZ = stackalloc sbyte[Ddeml.MAX_STRING_SIZE];
int len = Ddeml.DdeQueryString(_InstanceId, hsz1, pSZ, Ddeml.MAX_STRING_SIZE, Ddeml.CP_WINANSI);
string topic = new string(pSZ);
len = Ddeml.DdeQueryString(_InstanceId, hsz2, pSZ, Ddeml.MAX_STRING_SIZE, Ddeml.CP_WINANSI);
string item = new string(pSZ);
byte* bt = stackalloc byte[Ddeml.MAX_STRING_SIZE];
len = Ddeml.DdeGetData(hData, bt, Ddeml.MAX_STRING_SIZE, 0);
byte[] bytes = new byte[len]; for (int i = 0; i < len; i++) { bytes[i] = *bt++; };
if (hData != IntPtr.Zero) Ddeml.DdeUnaccessData(hData);
return new IntPtr((int)OnAdvData(uFmt, topic, item, bytes));
}
case Ddeml.XTYP_ADVREQ:
unsafe
{
sbyte* pSZ = stackalloc sbyte[Ddeml.MAX_STRING_SIZE];
int len = Ddeml.DdeQueryString(_InstanceId, hsz1, pSZ, Ddeml.MAX_STRING_SIZE, Ddeml.CP_WINANSI);
string topic = new string(pSZ);
len = Ddeml.DdeQueryString(_InstanceId, hsz2, pSZ, Ddeml.MAX_STRING_SIZE, Ddeml.CP_WINANSI);
string item = new string(pSZ);
byte[] data = OnAdvReq(uFmt, topic, item);
// Create and return the data handle representing the data being advised.
if (data != null && data.Length > 0)
{
return Ddeml.DdeCreateDataHandle(_InstanceId, data, data.Length, 0, hsz2, uFmt, 0); ;
}
// This transaction could not be Ddeml.DDE_FACK here.
return IntPtr.Zero;
}
case Ddeml.XTYP_ADVSTART:
unsafe
{
// Get the item name from the hsz2 string handle.
sbyte* pSZ = stackalloc sbyte[Ddeml.MAX_STRING_SIZE];
int len = Ddeml.DdeQueryString(_InstanceId, hsz1, pSZ, Ddeml.MAX_STRING_SIZE, Ddeml.CP_WINANSI);
string topic = new string(pSZ);
len = Ddeml.DdeQueryString(_InstanceId, hsz2, pSZ, Ddeml.MAX_STRING_SIZE, Ddeml.CP_WINANSI);
string item = new string(pSZ);
// Get a value indicating whether an advise loop should be initiated from the subclass.
//AdvStart(hConv, item, uFmt, Ddeml.XTYPF_ACKREQ);
return OnAdvStart(uFmt, topic, item) ? new IntPtr(1) : IntPtr.Zero;
}
case Ddeml.XTYP_ADVSTOP:
unsafe
{
// Get the item name from the hsz2 string handle.
sbyte* pSZ = stackalloc sbyte[Ddeml.MAX_STRING_SIZE];
int len = Ddeml.DdeQueryString(_InstanceId, hsz1, pSZ, Ddeml.MAX_STRING_SIZE, Ddeml.CP_WINANSI);
string topic = new string(pSZ);
len = Ddeml.DdeQueryString(_InstanceId, hsz2, pSZ, Ddeml.MAX_STRING_SIZE, Ddeml.CP_WINANSI);
string item = new string(pSZ);
// Inform the subclass that the advise loop has been terminated.
//AdvStop(hConv, item, uFmt);
OnAdvStop(uFmt, topic, item);
break;
}
case Ddeml.XTYP_CONNECT:
unsafe
{
sbyte* pSZ = stackalloc sbyte[Ddeml.MAX_STRING_SIZE];
int len = Ddeml.DdeQueryString(_InstanceId, hsz1, pSZ, Ddeml.MAX_STRING_SIZE, Ddeml.CP_WINANSI);
string topic = new string(pSZ);
// Get a value from the subclass indicating whether the connection should be allowed.
return OnConnect(topic, new CONVCONTEXT(), true) ? new IntPtr(1) : IntPtr.Zero;
}
case Ddeml.XTYP_CONNECT_CONFIRM:
unsafe
{
sbyte* pSZ = stackalloc sbyte[Ddeml.MAX_STRING_SIZE];
int len = Ddeml.DdeQueryString(_InstanceId, hsz1, pSZ, Ddeml.MAX_STRING_SIZE, Ddeml.CP_WINANSI);
string topic = new string(pSZ);
// Create a Conversation object and add it to the hConv table.
_ConversationTable.Add(hConv);
// Inform the subclass that a hConv has been established.
OnConnectConfirm(topic, true);
break;
}
case Ddeml.XTYP_DISCONNECT:
{
// Remove the Conversation from the hConv table.
_ConversationTable.Remove(hConv);
// Inform the subclass that the hConv has been disconnected.
OnDisconnect(true);
// Return zero to indicate that there are no problems.
return IntPtr.Zero;
}
case Ddeml.XTYP_EXECUTE:
unsafe
{
// Get the command from the data handle.
sbyte* pSZ = stackalloc sbyte[Ddeml.MAX_STRING_SIZE];
int len = Ddeml.DdeQueryString(_InstanceId, hsz1, pSZ, Ddeml.MAX_STRING_SIZE, Ddeml.CP_WINANSI);
string topic = new string(pSZ);
byte* bt = stackalloc byte[Ddeml.MAX_STRING_SIZE];
len = Ddeml.DdeGetData(hData, bt, Ddeml.MAX_STRING_SIZE, 0);
string command = new string((sbyte*)bt);
// Send the command to the subclass and get the resul
return new IntPtr((int)OnExecute(topic, command.TrimEnd('\0')));
}
case Ddeml.XTYP_POKE:
unsafe
{
// Get the item name from the hsz2 string handle.
sbyte* pSZ = stackalloc sbyte[Ddeml.MAX_STRING_SIZE];
int len = Ddeml.DdeQueryString(_InstanceId, hsz1, pSZ, Ddeml.MAX_STRING_SIZE, Ddeml.CP_WINANSI);
string topic = new string(pSZ);
len = Ddeml.DdeQueryString(_InstanceId, hsz2, pSZ, Ddeml.MAX_STRING_SIZE, Ddeml.CP_WINANSI);
string item = new string(pSZ);
byte* bt = stackalloc byte[Ddeml.MAX_STRING_SIZE];
len = Ddeml.DdeGetData(hData, bt, Ddeml.MAX_STRING_SIZE, 0);
byte[] data = new byte[len]; for (int i = 0; i < len; i++) { data[i] = *bt++; };
// Send the data to the subclass and get the resul
return new IntPtr((int)OnPoke(uFmt, topic, item, data));
}
case Ddeml.XTYP_REQUEST:
unsafe
{
// Get the item name from the hsz2 string handle.
sbyte* pSZ = stackalloc sbyte[Ddeml.MAX_STRING_SIZE];
int len = Ddeml.DdeQueryString(_InstanceId, hsz1, pSZ, Ddeml.MAX_STRING_SIZE, Ddeml.CP_WINANSI);
string topic = new string(pSZ);
len = Ddeml.DdeQueryString(_InstanceId, hsz2, pSZ, Ddeml.MAX_STRING_SIZE, Ddeml.CP_WINANSI);
string item = new string(pSZ);
// Send the request to the subclass and get the resul
var result = OnRequest(uFmt, topic, item);
// Return a data handle if the subclass Ddeml.DDE_FACK the request successfully.
if (result != null)
{
return Ddeml.DdeCreateDataHandle(_InstanceId, result, result.Length, 0, hsz2, uFmt, 0);
}
// Return DDE_FDdeml.DDE_FNOTDdeml.DDE_FACK if the subclass did not process the command.
return new IntPtr(Ddeml.DDE_FNOTPROCESSED);
}
case Ddeml.XTYP_XACT_COMPLETE:
unsafe
{
sbyte* pSZ = stackalloc sbyte[Ddeml.MAX_STRING_SIZE];
int len = Ddeml.DdeQueryString(_InstanceId, hsz1, pSZ, Ddeml.MAX_STRING_SIZE, Ddeml.CP_WINANSI);
string topic = new string(pSZ);
len = Ddeml.DdeQueryString(_InstanceId, hsz2, pSZ, Ddeml.MAX_STRING_SIZE, Ddeml.CP_WINANSI);
string item = new string(pSZ);
OnXactComplete(uFmt, topic, item, hData, dwData1);
break;
}
case Ddeml.XTYP_WILDCONNECT:
{
// This library does not support wild connects.
return IntPtr.Zero;
}
case Ddeml.XTYP_ERROR:
{
// Get the error code, but do nothing with it at this time.
return IntPtr.Zero;
}
case Ddeml.XTYP_REGISTER:
unsafe
{
// Get the service name from the hsz1 string handle.
sbyte* pSZ = stackalloc sbyte[Ddeml.MAX_STRING_SIZE];
int len = Ddeml.DdeQueryString(_InstanceId, hsz1, pSZ, Ddeml.MAX_STRING_SIZE, Ddeml.CP_WINANSI);
string bas = new string(pSZ);
len = Ddeml.DdeQueryString(_InstanceId, hsz2, pSZ, Ddeml.MAX_STRING_SIZE, Ddeml.CP_WINANSI);
string inst = new string(pSZ);
OnRegister(bas, inst);
return IntPtr.Zero;
}
case Ddeml.XTYP_UNREGISTER:
unsafe
{
sbyte* pSZ = stackalloc sbyte[Ddeml.MAX_STRING_SIZE];
int len = Ddeml.DdeQueryString(_InstanceId, hsz1, pSZ, Ddeml.MAX_STRING_SIZE, Ddeml.CP_WINANSI);
string bas = new string(pSZ);
len = Ddeml.DdeQueryString(_InstanceId, hsz2, pSZ, Ddeml.MAX_STRING_SIZE, Ddeml.CP_WINANSI);
string inst = new string(pSZ);
OnUnRegister(bas, inst);
return IntPtr.Zero;
}
}
return IntPtr.Zero;
}
public void Advise(string topic, string item)
{
if (IsDisposed)
{
throw new ObjectDisposedException(this.GetType().ToString());
}
if (!IsRegistered)
{
throw new InvalidOperationException("NotRegisteredMessage");
}
if (topic == null || topic.Length > Ddeml.MAX_STRING_SIZE)
{
throw new ArgumentNullException("topic");
}
if (item == null || item.Length > Ddeml.MAX_STRING_SIZE)
{
throw new ArgumentNullException("item");
}
// Assume the topic name and item name are wild.
IntPtr topicHandle = topic != "*" ? Ddeml.DdeCreateStringHandle(_InstanceId, topic, Ddeml.CP_WINANSI) : IntPtr.Zero;
IntPtr itemHandle = item != "*" ? Ddeml.DdeCreateStringHandle(_InstanceId, item, Ddeml.CP_WINANSI) : IntPtr.Zero;
// Check the result to see if the post failed.
if (!Ddeml.DdePostAdvise(_InstanceId, topicHandle, itemHandle))
{
int error = Ddeml.DdeGetLastError(_InstanceId);
var msg = Ddeml.DDEGetErrorMsg(error); if (msg != null) { }
}
Ddeml.DdeFreeStringHandle(_InstanceId, itemHandle);
Ddeml.DdeFreeStringHandle(_InstanceId, topicHandle);
}
public void Pause(IntPtr hConv)
{
if (IsDisposed)
{
throw new ObjectDisposedException(this.GetType().ToString());
}
if (!IsRegistered)
{
throw new InvalidOperationException("NotRegisteredMessage");
}
// Check the result to see if the DDEML callback was disabled.
if (!Ddeml.DdeEnableCallback(_InstanceId, hConv, Ddeml.EC_DISABLE))
{
int error = Ddeml.DdeGetLastError(_InstanceId);
Ddeml.DDEGetErrorMsg(error);
}
}
public void Resume(IntPtr hConv)
{
if (IsDisposed)
{
throw new ObjectDisposedException(this.GetType().ToString());
}
if (!IsRegistered)
{
throw new InvalidOperationException("NotRegisteredMessage");
}
// Check the result to see if the DDEML callback was enabled.
if (!Ddeml.DdeEnableCallback(_InstanceId, hConv, Ddeml.EC_ENABLEALL))
{
int error = Ddeml.DdeGetLastError(_InstanceId);
Ddeml.DDEGetErrorMsg(error);
}
}
public void Disconnect(IntPtr hConv)
{
if (IsDisposed)
{
throw new ObjectDisposedException(this.GetType().ToString());
}
if (!IsRegistered)
{
throw new InvalidOperationException("NotRegisteredMessage");
}
if (_ConversationTable.Contains(hConv))
{
// Terminate the hConv.
Ddeml.DdeDisconnect(hConv);
// Remove the Conversation from the hConv table.
_ConversationTable.Remove(hConv);
}
}
public void Disconnect()
{
if (IsDisposed)
{
throw new ObjectDisposedException(this.GetType().ToString());
}
if (!IsRegistered)
{
throw new InvalidOperationException("NotRegisteredMessage");
}
// Terminate all conversations.
foreach (IntPtr hConv in _ConversationTable)
{
Ddeml.DdeDisconnect(hConv);
}
// clear the hConv table.
_ConversationTable.Clear();
}
// Démarre une transaction Advise.
public bool AdvStart(IntPtr hConv, string item, ConversionFormat wFormat, int wFlag)
{
if (!IsRegistered)
return false;
int res = 0;
// Création de la chaîne DDE de l'élément.
IntPtr hszItem = Ddeml.DdeCreateStringHandle(_InstanceId, item, Ddeml.CP_WINANSI);
if ((hszItem == IntPtr.Zero) && (item.Length != 0))
return false;
// Exécution de la transaction.
Ddeml.DdeClientTransaction(null, 0, hConv, hszItem, wFormat, (wFlag == Ddeml.XTYPF_ACKREQ) ?
(Ddeml.XTYP_ADVSTARTACKREQ) : ((wFlag == Ddeml.XTYPF_NODATA) ? (Ddeml.XTYP_ADVSTARTNODATA) : (Ddeml.XTYP_ADVSTART)), Ddeml.TIMEOUT_ASYNC, ref res);
// Libération de la chaîne DDE.
if (hszItem != IntPtr.Zero)
Ddeml.DdeFreeStringHandle(_InstanceId, hszItem);
return res != 0;
}
// Arrête une transaction Advise.
public void AdvStop(IntPtr hConv, string item, ConversionFormat wFormat)
{
if (!IsRegistered)
return;
// Création de la chaîne DDE de l'élément.
IntPtr hszItem = Ddeml.DdeCreateStringHandle(_InstanceId, item, Ddeml.CP_WINANSI);
if ((hszItem == IntPtr.Zero) && (item.Length != 0))
return;
// Exécution de la transaction.
int res = 0;
Ddeml.DdeClientTransaction(null, 0, hConv, hszItem, wFormat, Ddeml.XTYP_ADVSTOP, Ddeml.TIMEOUT_ASYNC, ref res);
// Libération de la chaîne DDE.
if (hszItem != IntPtr.Zero)
Ddeml.DdeFreeStringHandle(_InstanceId, hszItem);
}
protected virtual DDEResult OnAdvData(ConversionFormat uFormat, string topic, string item, byte[] data)
{
return DDEResult.FNOTPROCESSED;
}
protected virtual byte[] OnAdvReq(ConversionFormat uFormat, string topic, string item)
{
return null;
}
protected virtual bool OnAdvStart(ConversionFormat uFormat, string topic, string item)
{
return true;
}
protected virtual void OnAdvStop(ConversionFormat uFormat, string topic, string item)
{
}
protected virtual bool OnConnect(string topic, CONVCONTEXT context, bool sameInstance)
{
return true;
}
protected virtual void OnConnectConfirm(string topic, bool sameInstance)
{
}
protected virtual void OnDisconnect(bool sameInstance)
{
}
protected virtual void OnError(ushort errorCode)
{
}
protected virtual DDEResult OnExecute(string topic, string command)
{
return DDEResult.FNOTPROCESSED;
}
protected virtual byte[] OnRequest(ConversionFormat ufmt, string topic, string item)
{
return null;
}
protected virtual DDEResult OnPoke(ConversionFormat uFormat, string topic, string item, byte[] data)
{
return DDEResult.FNOTPROCESSED;
}
protected virtual void OnRegister(string baseServiceName, string instanceServiceName)
{
}
protected virtual void OnUnRegister(string baseServiceName, string instanceServiceName)
{
}
protected virtual void OnXactComplete(ConversionFormat uFormat, string topic, string item, IntPtr data, uint transactionID)
{
}
protected virtual void OnCallback(MONCBSTRUCT mon)
{
}
protected virtual void OnConversation(MONCONVSTRUCT mon)
{
}
protected virtual void OnError(MONERRSTRUCT mon)
{
}
protected virtual void OnString(MONHSZSTRUCT mon)
{
}
protected virtual void OnLink(MONLINKSTRUCT mon)
{
}
protected virtual void OnPost(MONMSGSTRUCT mon)
{
}
protected virtual void OnSend(MONMSGSTRUCT mon)
{
}
/// <summary>
/// This class is needed to dispose of DDEML resources correctly since the DDEML is thread specific.
} // class
}