C# WMEncoder Error C00D002E - c#

I wrote a Windows Service Code to convert AVI to WMV file.
I'm using XviD, and Windows Media 9, in a Windows Vista.
The service starts converting, and stops before completing the job, with a WMENC_ENCODER_STOPPED status, but the file was not fully converted. And the resulting file stays locked to this application forever, until I stop the service. And when I do so, that resulting file is automatically deleted.
Can anyone, please, help? It is important !!
EDIT: This program works constantly, executing conversion tasks. If a file fails to convert as described above, and I use the function GC.WaitForPendingFinalizers(), I still have the locking problem. But the service goes on, and pick another task. Succeeding or failing this new task, when I use GC.WaitForPendingFinalizers(), the first one is unlocked, and automatically deleted.
public WMENC_ENCODER_STATE EncoderStatus
{
get { return _encoderStatus; }
set { _encoderStatus = value; }
}
IWMEncFile2 File;
IWMEncAudioSource[] SrcAud;
IWMEncVideoSource2[] SrcVid;
IWMEncSourceGroup2[] SrcGrp;
WMEncoder glbEncoder = new WMEncoder();
WMEncProfile2[] Pro = null;
IWMEncSourceGroupCollection SrcGrpColl = null;
public WMVEnconder(ConvertGroup[] sourceGroup, string targetFile, string profilefile)
{
glbEncoder.OnStateChange += new _IWMEncoderEvents_OnStateChangeEventHandler(Encoder_OnStateChange);
glbEncoder.OnError +=new _IWMEncoderEvents_OnErrorEventHandler(Encoder_OnError);
// Creates source group.
SrcGrpColl = glbEncoder.SourceGroupCollection;
int[] intProVBRModeAudio = new int[sourceGroup.Length];
int[] intProVBRModeVideo = new int[sourceGroup.Length];
Int32[] intProContentType = new int[sourceGroup.Length];
Pro = new WMEncProfile2[sourceGroup.Length];
for (int i = 0; i < Pro.Length; i++)
{
Pro[i] = new WMEncProfile2();
Pro[i].LoadFromMemory(profilefile);
intProContentType[i] = Pro[i].ContentType;
intProVBRModeAudio[i] = (int)Pro[i].get_VBRMode(WMENC_SOURCE_TYPE.WMENC_AUDIO, 0);
intProVBRModeVideo[i] = (int)Pro[i].get_VBRMode(WMENC_SOURCE_TYPE.WMENC_VIDEO, 0);
}
SrcGrp = new IWMEncSourceGroup2[sourceGroup.Length];
for (int i = 0; i < SrcGrp.Length; i++)
SrcGrp[i] = (IWMEncSourceGroup2)SrcGrpColl.Add(i.ToString());
SrcAud = new IWMEncAudioSource[sourceGroup.Length];
SrcVid = new IWMEncVideoSource2[sourceGroup.Length];
for (int i = 0; i < sourceGroup.Length; i++)
{
if (intProContentType[i] == 1)
{
SrcAud[i] = (WMEncoderLib.IWMEncAudioSource)SrcGrp[i].AddSource(WMENC_SOURCE_TYPE.WMENC_AUDIO);
SrcAud[i].SetInput(sourceGroup[i].AudioFile, "", "");
SrcAud[i].MarkIn = sourceGroup[i].AudioMarkin;
SrcAud[i].MarkOut = sourceGroup[i].AudioMarkout;
}
else if (intProContentType[i] == 16)
{
SrcVid[i] = (WMEncoderLib.IWMEncVideoSource2)SrcGrp[i].AddSource(WMENC_SOURCE_TYPE.WMENC_VIDEO);
SrcVid[i].SetInput(sourceGroup[i].VideoFile, "", "");
SrcVid[i].MarkIn = sourceGroup[i].VideoMarkin;
SrcVid[i].MarkOut = sourceGroup[i].VideoMarkout;
}
else if (intProContentType[i] == 17)
{
SrcAud[i] = (WMEncoderLib.IWMEncAudioSource)SrcGrp[i].AddSource(WMENC_SOURCE_TYPE.WMENC_AUDIO);
SrcAud[i].SetInput(sourceGroup[i].AudioFile, "", "");
SrcVid[i] = (WMEncoderLib.IWMEncVideoSource2)SrcGrp[i].AddSource(WMENC_SOURCE_TYPE.WMENC_VIDEO);
SrcVid[i].SetInput(sourceGroup[i].VideoFile, "", "");
SrcAud[i].MarkIn = sourceGroup[i].AudioMarkin;
SrcAud[i].MarkOut = sourceGroup[i].AudioMarkout;
SrcVid[i].MarkIn = sourceGroup[i].VideoMarkin;
SrcVid[i].MarkOut = sourceGroup[i].VideoMarkout;
}
else
throw new Exception("erro - não suporta este tipo de profile");
SrcGrp[i].set_Profile(Pro[i]);
if (i + 1 < sourceGroup.Length)
SrcGrp[i].SetAutoRollover(-1, (i + 1).ToString());
}
File = (IWMEncFile2)glbEncoder.File;
File.LocalFileName = targetFile;
glbEncoder.PrepareToEncode(true);
}
/// <summary>
/// Inicia os processos do codificador.
/// </summary>
public void Start()
{
glbEncoder.Start();
}
/// <summary>
/// Pára os processos do codificador.
/// </summary>
public void Stop()
{
glbEncoder.Stop();
}
/// <summary>
/// Elimina as configurações atuais.
/// </summary>
public void Reset()
{
glbEncoder.Reset();
}
/// <summary>
/// Evento deflagrado pela mudança no estado do codificador.
/// </summary>
/// <param name="enumState">Enumerador indicador do estado do codificador.</param>
private void Encoder_OnStateChange(WMEncoderLib.WMENC_ENCODER_STATE enumState)
{
_encoderStatus = enumState;
string strRunState = "";
switch (enumState)
{
case WMENC_ENCODER_STATE.WMENC_ENCODER_STARTING :
strRunState = "Encoder Starting";
break;
case WMENC_ENCODER_STATE.WMENC_ENCODER_RUNNING:
strRunState = "Encoder Running";
break;
case WMENC_ENCODER_STATE.WMENC_ENCODER_END_PREPROCESS:
strRunState = "Encoder End Preprocess";
break;
case WMENC_ENCODER_STATE.WMENC_ENCODER_PAUSING:
strRunState = "Encoder Pausing";
break;
case WMENC_ENCODER_STATE.WMENC_ENCODER_PAUSED:
strRunState = "Encoder Paused";
break;
case WMENC_ENCODER_STATE.WMENC_ENCODER_STOPPING:
strRunState = "Encoder Stopping";
break;
case WMENC_ENCODER_STATE.WMENC_ENCODER_STOPPED:
strRunState = "Encoder Stopped";
break;
}
_strEncoderStatus = strRunState;
}
/// <summary>
/// Evento deflagrado pela ocorrência de um erro durante o processo de codificação.
/// </summary>
/// <param name="hr">Valor numérico do erro que ocorreu.</param>
private void Encoder_OnError(int hr)
{
string errorReceived = "";
switch (hr)
{
case -1074600792:
errorReceived = "The starting time must be greater than zero and less than the ending time.";
break;
case -1074600793:
errorReceived = "The ending time must be greater than the starting time and less than the file duration.";
break;
case -1074600804:
errorReceived = "The video capture device is in use and cannot be opened.";
break;
case -1074600808:
errorReceived = "The video capture driver returned an unrecoverable error.";
break;
case -1074600809:
errorReceived = "The video capture device did not start.";
break;
case -1074600813:
errorReceived = "The video capture window was not created.";
break;
case -1074600820:
errorReceived = "The specified operation is not allowed when the file is being archived.";
break;
case -1074600825:
errorReceived = "The archive file name is not properly specified.";
break;
case -1074600831:
errorReceived = "The specified operation is not allowed when the encoder engine is not running.";
break;
case -1074600833:
errorReceived = "Inverse telecine cannot be specified when the frame rate does not equal 30 frames per second.";
break;
case -1074600834:
errorReceived = "Internal problems are preventing the preview or postview.";
break;
case -1074600835:
errorReceived = "One or more required codecs cannot be found.";
break;
case -1074600840:
errorReceived = "The display size or color setting has changed since the encoding session was defined.";
break;
}
try
{
if (Directory.Exists(#"C:\MediaDNA_V2\Data\Conversion\Exception"))
{
MediaDNAException mdnaException = new MediaDNAException(Modulo.CONVERSION, 0, "C_110018", TipoErro.INFORMACAO, new Exception(errorReceived),
ErrorMessageConstants.C_110018, new object[] { hr.ToString("X") });
ExceptionManager.RegisterException(mdnaException, #"C:\MediaDNA_V2\Data\Conversion\Exception");
}
}
catch { }
}
#region IDisposable Members
/// <summary> Release everything. </summary>
public void Dispose()
{
glbEncoder.Stop();
glbEncoder.Reset();
GC.SuppressFinalize(this);
CloseInterfaces();
}
#endregion
/// <summary>
/// Fecha as interfaces utilizadas pelo codificador.
/// Obs: O processador precisa estar "STOPPED" para executar este processo.
/// </summary>
private void CloseInterfaces()
{
if (glbEncoder != null)
{
if (File != null)
Marshal.FinalReleaseComObject(File);
if (Pro != null)
{
for (int i = 0; i < Pro.Length; i++)
Marshal.FinalReleaseComObject(Pro[i]);
Pro = null;
}
if (SrcVid != null)
{
for (int i = 0; i < SrcVid.Length; i++)
Marshal.FinalReleaseComObject(SrcVid[i]);
SrcVid = null;
}
if (SrcAud != null)
{
for (int i = 0; i < SrcAud.Length; i++)
Marshal.FinalReleaseComObject(SrcAud[i]);
SrcAud = null;
}
if (SrcGrpColl != null)
Marshal.FinalReleaseComObject(SrcGrpColl);
if (SrcGrpColl != null)
Marshal.FinalReleaseComObject(SrcGrpColl);
if (glbEncoder != null)
Marshal.FinalReleaseComObject(glbEncoder);
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
}
}

I attached my function which I converted wav file to wma.
Make sure that you do encoder.Stop(); and encoder.Reset(); and off-course release all your com object.
I'm pretty sure that once you will released your COM object your issue will be fixed.
// Create a WMEncoder object.
WMEncoder encoder = new WMEncoder();
// Retrieve the source group collection.
IWMEncSourceGroupCollection srcGrpColl = encoder.SourceGroupCollection;
// Add a source group to the collection.
IWMEncSourceGroup srcGrp = srcGrpColl.Add("SG_1");
// Add a video and audio source to the source group.
IWMEncSource srcAud = srcGrp.AddSource(WMENC_SOURCE_TYPE.WMENC_AUDIO);
srcAud.SetInput(wavFileName, "", "");
// Specify a file object in which to save encoded content.
IWMEncFile file = encoder.File;
file.LocalFileName = wmaFileName;
// Create a profile collection object from the WMEncoder object.
encoder.ProfileCollection.ProfileDirectory =
string.Format("{0}Profiles", Request.PhysicalApplicationPath);
encoder.ProfileCollection.Refresh();
IWMEncProfileCollection proColl = encoder.ProfileCollection;
// Create a profile object
IEnumerator profEnum = proColl.GetEnumerator();
IWMEncProfile profile = null; ;
IWMEncProfile2 newProfile = null;
while (profEnum.MoveNext())
{
profile = (IWMEncProfile)profEnum.Current;
if (profile.Name == "WavToWma")
{
// Load profile
newProfile = new WMEncProfile2();
newProfile.LoadFromIWMProfile(profile);
// Specify this profile object as the profile to use in source group.
srcGrp.set_Profile(newProfile);
}
}
// Start the encoding process.
// Wait until the encoding process stops before exiting the application.
encoder.PrepareToEncode(true);
encoder.Start();
while (encoder.RunState != WMENC_ENCODER_STATE.WMENC_ENCODER_STOPPED)
{
Thread.Sleep(500);
}
encoder.Stop();
encoder.Reset();
try
{
#region Release com objects
if (file != null)
Marshal.FinalReleaseComObject(file);
if (profile != null)
Marshal.FinalReleaseComObject(profile);
if (newProfile != null)
Marshal.FinalReleaseComObject(newProfile);
if (srcAud != null)
Marshal.FinalReleaseComObject(srcAud);
if (srcGrp != null)
Marshal.FinalReleaseComObject(srcGrp);
if (srcGrpColl != null)
Marshal.FinalReleaseComObject(srcGrpColl);
if (proColl != null)
Marshal.FinalReleaseComObject(proColl);
if (encoder != null)
Marshal.FinalReleaseComObject(encoder);
// GC collect is explicitly called because of a memory leak issue of WMEncoder.
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
#endregion
}
catch { }
}

Related

How to read Memo type field from DBF file in C#?

I am unable to find the solution that how to read Memo type field from DBF file.
As per my current RnD, I found out that the "Memo" data is stored in a separate file with the same name as the DBF file, but with a ".fpt" file extension. The "Memo" data file consists of a series of blocks, each of which contains a fixed number of bytes (typically 512 bytes), and each block is identified by a block number.
The "Memo" column in the DBF file contains a 10-byte pointer that identifies the block number of the first block of data for the corresponding record. If the data for a particular record exceeds the size of one block, additional blocks are allocated as needed and linked together using the pointers stored in the first block.
Moreover I used the source code provided here
using System;
using System.Collections.Generic;
using System.Data;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
namespace System.IO
{
/// <summary>
/// This class reads a dbf files
/// </summary>
public class DBFReader : IDisposable
{
private BinaryReader reader;
private Encoding encoding;
public DBFReader(Stream stream, Encoding encoding)
{
this.encoding = encoding;
this.reader = new BinaryReader(stream, encoding);
ReadHeader();
}
public DBFReader(string filename, Encoding encoding)
{
if (File.Exists(filename) == false)
throw new FileNotFoundException();
this.encoding = encoding;
var bs = new BufferedStream(File.OpenRead(filename));
this.reader = new BinaryReader(bs, encoding);
ReadHeader();
}
private void ReadHeader()
{
byte[] buffer = reader.ReadBytes(Marshal.SizeOf(typeof(DBFHeader)));
// Marshall the header into a DBFHeader structure
GCHandle handle = GCHandle.Alloc(buffer, GCHandleType.Pinned);
this.header = (DBFHeader)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(DBFHeader));
handle.Free();
fields = new List<DBFFieldDescriptor>();
while (reader.PeekChar() != 13)
{
buffer = reader.ReadBytes(Marshal.SizeOf(typeof(DBFFieldDescriptor)));
handle = GCHandle.Alloc(buffer, GCHandleType.Pinned);
var fieldDescriptor = (DBFFieldDescriptor)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(DBFFieldDescriptor));
if ((fieldDescriptor.Flags & DBFFieldFlags.System) != DBFFieldFlags.System )
{
fields.Add(fieldDescriptor);
}
handle.Free();
}
byte headerTerminator = reader.ReadByte();
byte[] backlink = reader.ReadBytes(263);
}
private void ReadRecords()
{
records = new List<Dictionary<DBFFieldDescriptor, object>>();
// Skip back to the end of the header.
reader.BaseStream.Seek(header.HeaderLenght, SeekOrigin.Begin);
for (int i = 0; i < header.NumberOfRecords; i++)
{
if (reader.PeekChar() == '*') // DELETED
{
continue;
}
var record = new Dictionary<DBFFieldDescriptor, object>();
var row = reader.ReadBytes(header.RecordLenght);
foreach (var field in fields)
{
byte[] buffer = new byte[field.FieldLength];
Array.Copy(row, field.Address, buffer, 0, field.FieldLength);
string text = (encoding.GetString(buffer) ?? String.Empty).Trim();
switch ((DBFFieldType)field.FieldType)
{
case DBFFieldType.Character:
record[field] = text;
break;
case DBFFieldType.Currency:
if (String.IsNullOrWhiteSpace(text))
{
if ((field.Flags & DBFFieldFlags.AllowNullValues) == DBFFieldFlags.AllowNullValues)
{
record[field] = null;
}
else
{
record[field] = 0.0m;
}
}
else
{
record[field] = Convert.ToDecimal(text);
}
break;
case DBFFieldType.Numeric:
case DBFFieldType.Float:
if (String.IsNullOrWhiteSpace(text))
{
if ((field.Flags & DBFFieldFlags.AllowNullValues) == DBFFieldFlags.AllowNullValues)
{
record[field] = null;
}
else
{
record[field] = 0.0f;
}
}
else
{
record[field] = Convert.ToSingle(text);
}
break;
case DBFFieldType.Date:
if (String.IsNullOrWhiteSpace(text))
{
if ((field.Flags & DBFFieldFlags.AllowNullValues) == DBFFieldFlags.AllowNullValues)
{
record[field] = null;
}
else
{
record[field] = DateTime.MinValue;
}
}
else
{
record[field] = DateTime.ParseExact(text, "yyyyMMdd", CultureInfo.InvariantCulture);
}
break;
case DBFFieldType.DateTime:
if (String.IsNullOrWhiteSpace(text) || BitConverter.ToInt64(buffer, 0) == 0)
{
if ((field.Flags & DBFFieldFlags.AllowNullValues) == DBFFieldFlags.AllowNullValues)
{
record[field] = null;
}
else
{
record[field] = DateTime.MinValue;
}
}
else
{
record[field] = JulianToDateTime(BitConverter.ToInt64(buffer, 0));
}
break;
case DBFFieldType.Double:
if (String.IsNullOrWhiteSpace(text))
{
if ((field.Flags & DBFFieldFlags.AllowNullValues) == DBFFieldFlags.AllowNullValues)
{
record[field] = null;
}
else
{
record[field] = 0.0;
}
}
else
{
record[field] = Convert.ToDouble(text);
}
break;
case DBFFieldType.Integer:
if (String.IsNullOrWhiteSpace(text))
{
if ((field.Flags & DBFFieldFlags.AllowNullValues) == DBFFieldFlags.AllowNullValues)
{
record[field] = null;
}
else
{
record[field] = 0;
}
}
else
{
record[field] = BitConverter.ToInt32(buffer, 0);
}
break;
case DBFFieldType.Logical:
if (String.IsNullOrWhiteSpace(text))
{
if ((field.Flags & DBFFieldFlags.AllowNullValues) == DBFFieldFlags.AllowNullValues)
{
record[field] = null;
}
else
{
record[field] = false;
}
}
else
{
record[field] = (buffer[0] == 'Y' || buffer[0] == 'T');
}
break;
case DBFFieldType.Memo:
case DBFFieldType.General:
case DBFFieldType.Picture:
default:
record[field] = buffer;
break;
}
}
records.Add(record);
}
}
public DataTable ReadToDataTable()
{
ReadRecords();
var table = new DataTable();
// Columns
foreach (var field in fields)
{
var colType = ToDbType(field.FieldType);
var column = new DataColumn(field.FieldName, colType ?? typeof(String));
table.Columns.Add(column);
}
// Rows
foreach (var record in records)
{
var row = table.NewRow();
foreach (var column in record.Keys)
{
row[column.FieldName] = record[column] ?? DBNull.Value;
}
table.Rows.Add(row);
}
return table;
}
public IEnumerable<Dictionary<string, object>> ReadToDictionary()
{
ReadRecords();
return records.Select(record => record.ToDictionary(r => r.Key.FieldName, r => r.Value)).ToList();
}
public IEnumerable<T> ReadToObject<T>()
where T : new()
{
ReadRecords();
var type = typeof(T);
var list = new List<T>();
foreach (var record in records)
{
T item = new T();
foreach (var pair in record.Select(s => new { Key = s.Key.FieldName, Value = s.Value }))
{
var property = type.GetProperty(pair.Key, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);
if (property != null)
{
if (property.PropertyType == pair.Value.GetType())
{
property.SetValue(item, pair.Value, null);
}
else
{
if (pair.Value != DBNull.Value)
{
property.SetValue(item, System.Convert.ChangeType(pair.Value, property.PropertyType), null);
}
}
}
}
list.Add(item);
}
return list;
}
private DBFHeader header;
private List<DBFFieldDescriptor> fields = new List<DBFFieldDescriptor>();
private List<Dictionary<DBFFieldDescriptor, object>> records = new List<Dictionary<DBFFieldDescriptor,object>>();
#region IDisposable
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected void Dispose(bool disposing)
{
if (disposing == false) return;
if (reader != null)
{
reader.Close();
reader.Dispose();
reader = null;
}
}
~DBFReader()
{
Dispose(false);
}
#endregion
/// <summary>
/// Convert a Julian Date as long to a .NET DateTime structure
/// Implemented from pseudo code at http://en.wikipedia.org/wiki/Julian_day
/// </summary>
/// <param name="julianDateAsLong">Julian Date to convert (days since 01/01/4713 BC)</param>
/// <returns>DateTime</returns>
private static DateTime JulianToDateTime(long julianDateAsLong)
{
if (julianDateAsLong == 0) return DateTime.MinValue;
double p = Convert.ToDouble(julianDateAsLong);
double s1 = p + 68569;
double n = Math.Floor(4 * s1 / 146097);
double s2 = s1 - Math.Floor(((146097 * n) + 3) / 4);
double i = Math.Floor(4000 * (s2 + 1) / 1461001);
double s3 = s2 - Math.Floor(1461 * i / 4) + 31;
double q = Math.Floor(80 * s3 / 2447);
double d = s3 - Math.Floor(2447 * q / 80);
double s4 = Math.Floor(q / 11);
double m = q + 2 - (12 * s4);
double j = (100 * (n - 49)) + i + s4;
return new DateTime(Convert.ToInt32(j), Convert.ToInt32(m), Convert.ToInt32(d));
}
/// <summary>
/// This is the file header for a DBF. We do this special layout with everything
/// packed so we can read straight from disk into the structure to populate it
/// </summary>
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]
private struct DBFHeader
{
/// <summary>The version.</summary>
public readonly DBFVersion Version;
/// <summary>The update year.</summary>
public readonly byte UpdateYear;
/// <summary>The update month.</summary>
public readonly byte UpdateMonth;
/// <summary>The update day.</summary>
public readonly byte UpdateDay;
/// <summary>The number of records.</summary>
public readonly int NumberOfRecords;
/// <summary>The length of the header.</summary>
public readonly short HeaderLenght;
/// <summary>The length of the bytes records.</summary>
public readonly short RecordLenght;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)]
public readonly byte[] Reserved;
/// <summary>Table Flags</summary>
public readonly DBFTableFlags TableFlags;
/// <summary>Code Page Mark</summary>
public readonly byte CodePage;
/// <summary>Reserved, contains 0x00</summary>
public readonly short EndOfHeader;
}
public enum DBFVersion : byte
{
Unknown = 0,
FoxBase = 0x02,
FoxBaseDBase3NoMemo = 0x03,
VisualFoxPro = 0x30,
VisualFoxProWithAutoIncrement = 0x31,
dBase4SQLTableNoMemo = 0x43,
dBase4SQLSystemNoMemo = 0x63,
FoxBaseDBase3WithMemo = 0x83,
dBase4WithMemo = 0x8B,
dBase4SQLTableWithMemo = 0xCB,
FoxPro2WithMemo = 0xF5,
FoxBASE = 0xFB
}
[Flags]
public enum DBFTableFlags : byte
{
None = 0x00,
HasStructuralCDX = 0x01,
HasMemoField = 0x02,
IsDBC = 0x04
}
/// <summary>
/// This is the field descriptor structure. There will be one of these for each column in the table.
/// </summary>
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]
private struct DBFFieldDescriptor
{
/// <summary>The field name.</summary>
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 11)]
public readonly string FieldName;
/// <summary>The field type.</summary>
public readonly char FieldType;
/// <summary>The field address.</summary>
public readonly int Address;
/// <summary>The field length in bytes.</summary>
public readonly byte FieldLength;
/// <summary>The field precision.</summary>
public readonly byte DecimalCount;
/// <summary>Field Flags</summary>
public readonly DBFFieldFlags Flags;
/// <summary>AutoIncrement next value</summary>
public readonly int AutoIncrementNextValue;
/// <summary>AutoIncrement step value</summary>
public readonly byte AutoIncrementStepValue;
/// <summary>Reserved</summary>
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
public readonly byte[] Reserved;
public override string ToString()
{
return String.Format("{0} {1}", FieldName, FieldType);
}
}
[Flags]
public enum DBFFieldFlags : byte
{
None = 0x00,
System = 0x01,
AllowNullValues = 0x02,
Binary = 0x04,
AutoIncrementing = 0x0C
}
public enum DBFFieldType : int
{
Character = 'C',
Currency = 'Y',
Numeric = 'N',
Float = 'F',
Date = 'D',
DateTime = 'T',
Double = 'B',
Integer = 'I',
Logical = 'L',
Memo = 'M',
General = 'G',
Picture = 'P'
}
public static Type ToDbType(char type)
{
switch ((DBFFieldType)type)
{
case DBFFieldType.Float:
return typeof(float);
case DBFFieldType.Integer:
return typeof(int);
case DBFFieldType.Currency:
return typeof(decimal);
case DBFFieldType.Character:
case DBFFieldType.Memo:
return typeof(string);
case DBFFieldType.Date:
case DBFFieldType.DateTime:
return typeof(DateTime);
case DBFFieldType.Logical:
return typeof(bool);
case DBFFieldType.General:
case DBFFieldType.Picture:
return typeof(byte[]);
default:
return null;
}
}
}
}
What is your question?
(Would be a mess as comment)
Your information about FPT file structure is not entirely correct and full details exist in the VFP help file.
Do you have to read it low level? If you have to, basically what you do is:
Collect memo block numbers from the DBF itself (block numbers are stored in corresponding field in DBF),
For a memo entry, go to block number in FPT (depends on block size)
From the block header read the size and get the bytes that matches the stored size.
While doing that, always read as binary, because unlike C strings, VFP string values are not ASCIIZ values, they can even contain '\x0'.
If it is not a must to read that way then the easy way is to simply read it using VFPOLEDB OleDb driver. ie:
void Main()
{
string cn = #"Provider=VFPOLEDB;Data source=C:\PROGRAM FILES (X86)\MICROSOFT VISUAL FOXPRO 9\SAMPLES\DATA;";
string query = "select emp_id, first_name, last_name, notes from Employee";
DataTable t = new DataTable();
new OleDbDataAdapter(query, cn).Fill(t);
foreach (var row in t.AsEnumerable())
{
Console.WriteLine($"{row["emp_id"]}, {row["first_name"]}, {row["last_name"]}, ({row["notes"]})");
}
}
Note that VFPOLEDB driver is 32 bits, you would need to target x86 platform. There are 64 bits drivers from Sybase ADS as they say (have never used).
PS: Also search for Tom Brother's Linq To VFp, Linq To EF VFP drivers.
PS2: I quickly glanced the source code you provided, and beware that is for older VFP versions and is not really correct (ie: Assumes Memo is a string which is not correct - if you accept them string in C# you are likely going to lose many data).

.NET MAUI app on Windows platform getting System.IO.FileNotFoundException' in System.Private.CoreLib.dll

I'm working on a .NET MAUI project in the context of MVVM architecture and I created two .txt files (15.txt and 19.txt) inside directory Resources\Raw. I set the files' Build action to MauiAsset. Each of
these two files contain topology for a matrix of color-coded fields.
What were I expecting: to read the text file's content without obtaining System.IO.FileNotFoundException' in System.Private.CoreLib.dll and to be able to use (invoke) the reader method more than once in a successfull manner.
I experience the following behaviour: in case the mentioned exception is not checked under Debug -> Windows -> Exception Setings -> Common Language Runtime Exceptions then the textfile's content is successfully obtained via the reader method as it's evident by looking at the graphical changes on one of my ContentPages (a matrix of fields appears with plenty of differently colored fields), however at the moment of its invokation an System.IO.FileNotFoundException' in System.Private.CoreLib.dllemerges as part of the logs (alerting message is absent).
However, if the aforementioned exception is checked under Debug -> Windows -> Exception Setings -> Common Language Runtime Exceptions
then my application does not even start succesfully.
Inside class GameViewModel:
/// <summary>
/// event handler method for choosing 15 as tablesize and reading appropriate table
/// </summary>
private async void GameViewModel_ButtonSmallTableClick(object? sender, System.EventArgs e)
{
await Navigation.PushAsync(new GamePage
{
BindingContext = _gameViewModel
});
Stream fileStream = await FileSystem.Current.OpenAppPackageFileAsync("15.txt");
await _gameModel.LoadGameAsync(fileStream);
_gameViewModel.GameTableNumRows = 15;
_gameViewModel.GameTableNumCols = 15;
_gameViewModel.GenerateFields();
}
/// <summary>
/// event handler method for choosing 19 as tablesize and reading appropriate table
/// </summary>
private async void GameViewModel_ButtonLargeTableClick(object? sender, System.EventArgs e)
{
await Navigation.PushAsync(new GamePage
{
BindingContext = _gameViewModel
});
Stream fileStream = await FileSystem.Current.OpenAppPackageFileAsync("19.txt");
await _gameModel.LoadGameAsync(fileStream);
_gameViewModel.GameTableNumRows = 19;
_gameViewModel.GameTableNumCols = 19;
_gameViewModel.GenerateFields();
}
Here's the method definition inside class GameModel:
/// <summary>
/// Loading colored matrix.
/// </summary>
/// <param name="fileStream">Type of Stream.</param>
public async Task LoadGameAsync(System.IO.Stream fileStream)
{
if (_dataAccess == null)
throw new InvalidOperationException("No data access is provided.");
var loadResult = await _dataAccess.LoadAsync(fileStream);
_gameTable = loadResult.Item1;
_player = loadResult.Item2;
_guards = loadResult.Item3;
IsWon = false;
}
Finally, here's the persistence-related method:
/// <summary>
/// Loading file
/// </summary>
/// <param name="path">Filepath.</param>
/// <returns>Gametable, Player, list of Guards.</returns>
public async Task<(UInt16[,], Player, List<Guard>)> LoadAsync(String path)
{
try
{
using (StreamReader reader = new StreamReader(path))
{
String[] gameFieldTypes;
String line = await reader.ReadLineAsync() ?? String.Empty;
Int32 tableSize = Int32.Parse(line);
UInt16[,] table = new UInt16[tableSize, tableSize];
Player player = new Player();
List<Guard> guards = new List<Guard>();
for (Int32 i = 0; i < tableSize; i++)
{
line = await reader.ReadLineAsync() ?? String.Empty;
gameFieldTypes = line.Split(' ');
for (Int32 j = 0; j < tableSize; j++)
{
if (gameFieldTypes[j] == "Empty")
{
table[j, i] = 0;
}
if (gameFieldTypes[j] == "Wall")
{
table[j, i] = 1;
}
if (gameFieldTypes[j] == "Guard")
{
table[j, i] = 2;
Guard guard = new Guard();
guard.X = j;
guard.Y = i;
guards.Add(guard);
}
if (gameFieldTypes[j] == "Player")
{
table[j, i] = 3;
player.X = j;
player.Y = i;
}
if (gameFieldTypes[j] == "Exit")
{
table[j, i] = 4;
}
}
}
return (table, player, guards);
}
}
catch
{
throw new GameDataException();
}
}
EDIT:
Here's the emerging exception: System.IO.FileNotFoundException' in System.Private.CoreLib.dll
Could not find file 'C:\WINDOWS\system32\SuspendedGame'.'
Line throwing exception inside method LoadAsync():
using (StreamReader reader = new StreamReader(path))
And here's the class within which the phrase "SuspendedGame" appears.
public class StoredGameBrowserModel
{
private IStore _store; // persistence
/// <summary>
/// Event of container being changed.
/// </summary>
public event EventHandler? StoreChanged;
public StoredGameBrowserModel(IStore store)
{
_store = store;
StoredGames = new List<StoredGameModel>();
}
/// <summary>
/// Query of the list of stored colored-field matrices.
/// </summary>
public List<StoredGameModel> StoredGames { get; private set; }
/// <summary>
/// Updating stored resource files.
/// </summary>
public async Task UpdateAsync()
{
if (_store == null)
return;
StoredGames.Clear();
// loading saved files
foreach (String name in await _store.GetFilesAsync())
{
if (name == "SuspendedGame") // we do not want to load this particular saved file
continue;
StoredGames.Add(new StoredGameModel
{
Name = name,
Modified = await _store.GetModifiedTimeAsync(name)
});
}
// arranging elements according to their date
StoredGames = StoredGames.OrderByDescending(item => item.Modified).ToList();
OnSavesChanged();
}
private void OnSavesChanged()
{
StoreChanged?.Invoke(this, EventArgs.Empty);
}
}
Solution:
inserting the following code at the beginning of the method body of LoadAsync():
if (!String.IsNullOrEmpty(_basePath))
path = Path.Combine(_basePath, path);
Reasoning:
the prefix library path was missing from the content of the method parameter.

How can I get the account number and password from the other class and check them for the login screen in c#

class Login
{
Account hesap = new Account(); // problem!?
TestData testData = new TestData(); // *
Hash hash = new Hash(); // *
int count= 0;
string userId, password; // problem !?
private void ozellikLogin()
{
hesap.HesapId = "326785";
sayac++;
if (count> 3)
{
Console.WriteLine("You entered 3 times incorretly!");
Environment.Exit(0);
}
if (userId == "" && password == "")
{
Console.WriteLine("Spaces aren't allowed!");
}
else if (userId.Length >= 6 || password.Length >= 8)
{
Console.WriteLine("You must enter a max of 6 digits and a maximum of 8 characters!");
}
else
{
if (userId == "bartu" && password == "1999")
{
Console.WriteLine("Giris Basarili!");
}
else
{
Console.WriteLine("Account number or password is wrong!");
}
}
}
User will enter their account number and password to connect to the system. and the entered password value will be hashed with the SHA256 hash algorithm, the auth.txt file will be opened and compared with the hash value next to the account number, it will be verified if it is equal. and will enter the system.
class Hash
{
private string Hashing(HashAlgorithm hashing,string inputBytes)
{
byte[] sourceBytes = hashing.ComputeHash(Encoding.UTF8.GetBytes(inputBytes));
StringBuilder stringBuilder = new StringBuilder("__Hash__");
for (int i = 0; i < sourceBytes.Length; i++)
{
stringBuilder.AppendLine(sourceBytes[i].ToString("x2"));
}
return stringBuilder.ToString();
}
private bool Karsilastir(string hash, string hesapId)
{
string hashTxt = "";
string[] satirlar = { };
try
{
satirlar = System.IO.File.ReadAllLines(#"C:\Users\bartu\Desktop\auth.txt");
}
catch (Exception e)
{
Console.WriteLine("Hata!", e.Message);
}
foreach (string i in satirlar)
{
string[] parcala = i.Split(',');
if (parcala[0].Equals(hesapId))
{
hashTxt = parcala[1];
break;
}
}
StringComparer karsilastir = StringComparer.OrdinalIgnoreCase;
return karsilastir.Compare(hashTxt, hash) == 0;
}
public bool Kontrol(string raw, string hesapId)
{
using (SHA256 sha256 = SHA256.Create())
{
string hash = Hashing(sha256, raw);
if (Karsilastir(hash, hesapId))
{
return true;
}else
{
return false;
}
}
}
}
auth.txt (example)
326785,af5e6187ff2fad1155074dd08b65a3b433432c0514e4422b5fafe8f9e664b0f7
400129,85c3016208d1854f7e8f1fa4e424cfd41ae5003b8d475947148951a93e3108af
388000,2b2282a5836e88e5ea443c4a0921c1ff19ba62df32402ce07db8ddf2946a0334
201005,9aba965a0939fde3b41dcb9ca45d146435fac718e016f08491ae57bddb3049b0
If the hash value of the password entered from the screen is not the same as in auth.txt, "User account number or password was entered incorrectly, try again" will be displayed.
If the same user logs in 3 times in 5 minutes, it will remain locked for 24 hours.
Only 6 digits should be entered in the account number field on the screen to connect to the system, and only 8 letters in length, small letters and numbers should be allowed for the password.
class Login
{
Account hesap = new Account(); // problem!?
TestData testData = new TestData(); // *
Hash hash = new Hash(); // *
int count= 0;
string userId, password; // problem !?
private void ozellikLogin()
{
hesap.HesapId = "326785";
sayac++;
if (count> 3)
{
Console.WriteLine("You entered 3 times incorretly!");
Environment.Exit(0);
}
if (userId == "" && password == "")
{
Console.WriteLine("Spaces aren't allowed!");
}
else if (userId.Length >= 6 || password.Length >= 8)
{
Console.WriteLine("You must enter a max of 6 digits and a maximum of 8 characters!");
}
else
{
if (userId == "bartu" && password == "1999")
{
Console.WriteLine("Giris Basarili!");
}
else
{
Console.WriteLine("Account number or password is wrong!");
}
}
}
And my TestDataClass
public class TestData
{
public void CustomerTest()
{
Customer ismailBorazan = new Customer("326785", "ismail Borazan","IsmB1982","TR610003200013900000326785",350.00,"TR300003200016420000326785",8000.00,null,0);
Musteri kamileHursitgilogullari = new Musteri("400129", "kamile Hurşitgilogullari", "12Hrst34", "TR610008324560000000400129", 2980.45, null, 0,null,0);
Customer zebercetBak = new Customer("388000", "Zebercet Bak", "Zb123456", "TR610007222250001200388000", 19150.00, "TR300007222249000001388000", 52.93, "TR300008222266600002388000", 2850.00);
Customer nazGulUcan = new Customer("201005", "Naz Gül Uçan", "Mordor99", "TR610032455466661200201005", 666.66, null, 0,"TR300032455410080003201005", 10000.00);
ListCustomer.customer.Add(ismailBorazan);
ListCustomer.customer.Add(kamileHursitgilogullari);
ListCustomer.customer.Add(zebercetBak);
ListCustomer.customer.Add(nazGulUcan);
if (File.Exists(#"C:\Users\bartu\Desktop\client.txt"))
return;
}
private void YazClientTxt()
{
try
{
var path = #"C:\Users\bartu\Desktop\client.txt"; // dosya yolu
StreamWriter fs = new StreamWriter(path); // dosyaya yazma
foreach (Customer item in ListCustomer.customer)
{
if (item.IbanTr != null)
{
fs.WriteLine(item.HesapNo, item.IbanTr, item.MiktarIbanTr);
//fs.WriteLine("{0}", "{1}", "{2}", item.HesapNo, item.IbanTr, item.MiktarIbanTr);
}
if (item.IbanEuro != null)
{
fs.WriteLine(item.HesapNo, item.IbanEuro, item.MiktarIbanEuro);
}
if (item.IbanUsd != null)
{
fs.WriteLine(item.HesapNo, item.IbanUsd, item.MiktarIbanUsd);
}
}
}
catch (Exception e)
{
Console.WriteLine("Hata!", e.Message);
}
}
}
My question may sound like it was too long at first, but I mentioned it for detailed understanding. I found it difficult to do the necessary checks while logging in to the login.cs class and could not find any examples.
This is a solution that doesn't persist every user login attempt, so every login attempt is kept in memory. It can be however changed without too much effort, so the login state can be persisted on file, disk, database, etc...
I started with an enumerated type with all the possible results of a login attempt:
/// <summary>
/// Possible result states of a login.
/// </summary>
internal enum LoginStatus : byte
{
Undefined = 0,
Success,
Failed,
UserLocked
}
Then I created a class whose instances are to keep the login status of an user, with all the properties needed to execute validation checks:
/// <summary>
/// Keeps the user current login status.
/// </summary>
internal class UserLoginStatus
{
/// <summary>
/// Used ID.
/// </summary>
public string UserId { get; set; }
/// <summary>
/// Signals the user is locked and cannot attempt any login for MINUTES_BEFORE_UNLOCK time.
/// </summary>
public bool UserIsLocked { get; set; }
/// <summary>
/// Number of failed attempts at login.
/// </summary>
public int FailedLoginCount { get; set; }
/// <summary>
/// Timestamp of the last login of the user.
/// </summary>
public DateTime? LastLoginTimeStamp { get; set; }
/// <summary>
/// Timestamp of the first login of the user.
/// </summary>
public DateTime? FirstLoginTimeStamp { get; set; }
}
The class LoginManager (static because it was easier for me) implements the login logic with all the necessary validation checks in the AttemptLogin function. A dictionary is used to keep all the failing attempt of a user to login. After a successful attempt at login, the entry for that user is removed from the dictionary:
/// <summary>
/// Manages users login attempts.
/// </summary>
internal static class LoginManager
{
/// <summary>
/// Time range in between a user is not allowed to input the credentials ATTEMPTS_BEFORE_LOCK times.
/// </summary>
private const double LOGIN_ATTEMPTS_SPAN_MINUTES = 5.0f;
/// <summary>
/// Number of login attempts before a user lock occours.
/// </summary>
private const byte ATTEMPTS_BEFORE_LOCK = 3;
/// <summary>
/// Minutes necessary before a user unlock is possible.
/// </summary>
private const int MINUTES_BEFORE_UNLOCK = 24 * 60;
/// <summary>
/// Dictionary holding the login status for each user.
/// </summary>
private static Dictionary<string, UserLoginStatus> _usersLogins = new Dictionary<string, UserLoginStatus>();
/// <summary>
/// Login logic.
/// </summary>
/// <param name="userId">User ID.</param>
/// <param name="password">User password.</param>
/// <param name="message">Login result description.</param>
public static LoginStatus AttemptLogin(string userId, string password, out string message)
{
message = null;
// 1) Get a current time timestamp.
var currentTime = DateTime.Now;
// 2) Check if we have an entry in the _usersLogins dictionary, if not, create it.
if (!_usersLogins.TryGetValue(userId, out UserLoginStatus userLogin))
{
// Create a new login status.
userLogin = new UserLoginStatus()
{
UserId = userId,
UserIsLocked = false,
FirstLoginTimeStamp = currentTime,
LastLoginTimeStamp = currentTime,
FailedLoginCount = 0
};
// Add a new login status for this user in the _usersLogins dictionary.
_usersLogins.Add(userId, userLogin);
}
// 3) Check if the user is locked.
if (userLogin.UserIsLocked)
{
// Check if the user can be unlocked.
var minutesSinceLastLogin = (currentTime - userLogin.LastLoginTimeStamp.Value).TotalMinutes;
if (minutesSinceLastLogin >= MINUTES_BEFORE_UNLOCK)
{
// Unlock the user by resetting his status.
userLogin.UserIsLocked = false;
userLogin.FailedLoginCount = 0;
userLogin.FirstLoginTimeStamp = currentTime;
userLogin.LastLoginTimeStamp = currentTime;
// Go on with the input validation...
}
else
{
// No, user can't be unlocked yet.
message = "User is locked out and must wait 24h before the next login attempt.";
userLogin.FailedLoginCount++;
return LoginStatus.UserLocked;
}
}
// ***************
// Validate input
// ***************
bool passwordOk = true;
// VALIDATION RULE A) Validation of non-empty userId.
if (passwordOk && string.IsNullOrEmpty(userId))
{
message = "Spaces aren't allowed in username.";
passwordOk = false;
}
// VALIDATION RULE B) Validation of non-empty password.
if (passwordOk && string.IsNullOrEmpty(password))
{
message = "Spaces aren't allowed in password.";
passwordOk = false;
}
// VALIDATION RULE C) Validation on userId and password length.
if (passwordOk && (userId.Length > 6 || password.Length > 8))
{
message = "You must enter a max of 6 digits (username) and a maximum of 8 characters (password).";
passwordOk = false;
}
// VALIDATION RULE D) Validation on lowercase characters.
if (passwordOk && userId.Any(char.IsUpper))
{
message = "UserId can't contain uppercase characters.";
passwordOk = false;
}
// VALIDATION RULE N) TODO....
// Effective password check.
if (passwordOk && !HashingManager.Kontrol(password, userId))
{
message = "Incorrect user/password.";
passwordOk = false;
}
if (!passwordOk)
{
// Validation failed.
userLogin.LastLoginTimeStamp = currentTime;
userLogin.FailedLoginCount++;
// Get the minutes passed since the first attempt.
var minsSinceFirstLogin = (currentTime - userLogin.FirstLoginTimeStamp.Value).TotalMinutes;
if (userLogin.FailedLoginCount == ATTEMPTS_BEFORE_LOCK && minsSinceFirstLogin <= LOGIN_ATTEMPTS_SPAN_MINUTES)
{
message += string.Format("\nUser is now locked out and must wait {0} minutes for the next login attempt.", MINUTES_BEFORE_UNLOCK);
userLogin.UserIsLocked = true;
return LoginStatus.UserLocked;
}
else
{
// If the user reached the maximum number of attemps, but waited more
// than LOGIN_TIME_SPAN_MINUTES, then reset his status to let him
// attempt the login for the next 3 times.
if (userLogin.FailedLoginCount == ATTEMPTS_BEFORE_LOCK)
{
userLogin.FirstLoginTimeStamp = currentTime;
userLogin.FailedLoginCount = 1;
}
}
// User still has some more attempts before being locked out.
return LoginStatus.Failed;
}
// The user successfully logged in.
// Remove the user from the login status dictionary.
_usersLogins.Remove(userId);
message = "User successfully logged in.";
return LoginStatus.Success;
}
}
These are your hashing functions, I just changed something in the Hashing function (the AppendLine was adding a newline character after every charater in the hash):
internal static class HashingManager
{
private static string Hashing(HashAlgorithm hashing, string sourceString)
{
byte[] sourceBytes = hashing.ComputeHash(Encoding.UTF8.GetBytes(sourceString));
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i < sourceBytes.Length; i++)
{
stringBuilder.Append(sourceBytes[i].ToString("x2"));
}
return stringBuilder.ToString();
}
private static string GetHashedPasswordForUser(string userId)
{
string[] authlines;
try
{
authlines = File.ReadAllLines(#"auth.txt");
}
catch (Exception e)
{
Console.WriteLine("Hata!", e.Message);
return null;
}
if (authlines is null || authlines.Length == 0)
{
Console.WriteLine("We also have a problem here!");
return null;
}
foreach (var auth in authlines)
{
var authTokens = auth.Split(',');
if (authTokens[0].Equals(userId))
return authTokens[1];
}
return null;
}
public static bool Kontrol(string rawPassword, string userId)
{
var hashedPw = GetHashedPasswordForUser(userId);
if (string.IsNullOrWhiteSpace(hashedPw))
{
// The user does not have an entry in the auth file.
return false;
}
using (SHA256 sha256 = SHA256.Create())
{
var hashedRawPw = Hashing(sha256, rawPassword);
StringComparer karsilastir = StringComparer.OrdinalIgnoreCase;
return karsilastir.Compare(hashedPw, hashedRawPw) == 0;
}
}
}
I tested it in a console application with this code:
do
{
Console.Write("User: ");
var userId = Console.ReadLine();
Console.Write("Password: ");
var pw = Console.ReadLine();
switch (LoginManager.AttemptLogin(userId, pw, out string message))
{
case LoginStatus.Failed:
Console.WriteLine(message);
break;
case LoginStatus.Success:
Console.WriteLine(message);
break;
case LoginStatus.UserLocked:
Console.WriteLine(message);
break;
}
Console.WriteLine();
}
while (true);

Debug .NET OutOfMemoryException with windbg

I need help to debug a OutOfMemoryException in a .net dll that convert rtf text in raw text or in html.
Here is the code for conversion, (http://matthewmanela.com/blog/converting-rtf-to-html/)
public string ConvertRtfToHtml(string rtfText)
{
if (rtfText.Equals("")) return "";
try
{
var thread = new Thread(ConvertRtfInSTAThread);
var threadData = new ConvertRtfThreadData { RtfText = rtfText };
thread.SetApartmentState(ApartmentState.STA);
thread.Start(threadData);
thread.Join();
return threadData.HtmlText;
}
catch (Exception e)
{
GestionErreurConv.EnregistrerErreur("Convert", "ConvertRtfToHtml", e.Message);
return rtfText;
}
}
private void ConvertRtfInSTAThread(object rtf)
{
try
{
var threadData = (ConvertRtfThreadData)rtf;
var converter = new RtfToHtmlConverter();
threadData.HtmlText = converter.ConvertRtfToHtml(threadData.RtfText);
}
catch (Exception e)
{
GestionErreurConv.EnregistrerErreur("Convert", "ConvertRtfToHtml", e.Message);
}
}
public class RtfToHtmlConverter
{
private const string FlowDocumentFormat = "<FlowDocument>{0}</FlowDocument>";
public string ConvertRtfToHtml(string rtfText)
{
var xamlText = string.Format(FlowDocumentFormat, ConvertRtfToXaml(rtfText));
var converter = new HtmlFromXamlConverter();
return converter.ConvertXamlToHtml(xamlText, false);
}
private string ConvertRtfToXaml(string rtfText)
{
string returnString;
try
{
var richTextBox = new RichTextBox
{
UndoLimit = 0,
IsUndoEnabled = false
};
var textRange = new TextRange(richTextBox.Document.ContentStart, richTextBox.Document.ContentEnd);
//Create a MemoryStream of the Rtf content
using (var rtfMemoryStream = new MemoryStream())
{
using (var rtfStreamWriter = new StreamWriter(rtfMemoryStream))
{
rtfStreamWriter.Write(rtfText);
rtfStreamWriter.Flush();
rtfMemoryStream.Seek(0, SeekOrigin.Begin);
//Load the MemoryStream into TextRange ranging from start to end of RichTextBox.
textRange.Load(rtfMemoryStream, DataFormats.Rtf);
}
}
using (var rtfMemoryStream = new MemoryStream())
{
textRange = new TextRange(richTextBox.Document.ContentStart, richTextBox.Document.ContentEnd);
textRange.Save(rtfMemoryStream, DataFormats.Xaml);
rtfMemoryStream.Seek(0, SeekOrigin.Begin);
using (var rtfStreamReader = new StreamReader(rtfMemoryStream)) {
returnString = rtfStreamReader.ReadToEnd();
}
}
// Libération mémoire
GC.Collect();
GC.WaitForPendingFinalizers();
return returnString;
}
catch (Exception)
{
// Libération mémoire
GC.Collect();
GC.WaitForPendingFinalizers();
return rtfText;
}
}
}
/// <summary>
/// HtmlToXamlConverter is a static class that takes an HTML string
/// and converts it into XAML
/// </summary>
public class HtmlFromXamlConverter
{
#region Public Methods
/// <summary>
/// Main entry point for Xaml-to-Html converter.
/// Converts a xaml string into html string.
/// </summary>
/// <param name="xamlString">
/// Xaml strinng to convert.
/// </param>
/// <returns>
/// Html string produced from a source xaml.
/// </returns>
public string ConvertXamlToHtml(string xamlString, bool asFullDocument)
{
var htmlStringBuilder = new StringBuilder(100);
using (var xamlReader = new XmlTextReader(new StringReader(xamlString)))
using (var htmlWriter = new XmlTextWriter(new StringWriter(htmlStringBuilder)))
{
if (!WriteFlowDocument(xamlReader, htmlWriter, asFullDocument))
{
return "";
}
return htmlStringBuilder.ToString();
}
}
#endregion Public Methods
// ---------------------------------------------------------------------
//
// Private Methods
//
// ---------------------------------------------------------------------
#region Private Methods
/// <summary>
/// Processes a root level element of XAML (normally it's FlowDocument element).
/// </summary>
/// <param name="xamlReader">
/// XmlTextReader for a source xaml.
/// </param>
/// <param name="htmlWriter">
/// XmlTextWriter producing resulting html
/// </param>
private bool WriteFlowDocument(XmlTextReader xamlReader, XmlTextWriter htmlWriter, bool asFullDocument)
{
if (!ReadNextToken(xamlReader))
{
// Xaml content is empty - nothing to convert
return false;
}
if (xamlReader.NodeType != XmlNodeType.Element || xamlReader.Name != "FlowDocument")
{
// Root FlowDocument elemet is missing
return false;
}
// Create a buffer StringBuilder for collecting css properties for inline STYLE attributes
// on every element level (it will be re-initialized on every level).
var inlineStyle = new StringBuilder();
if (asFullDocument)
{
htmlWriter.WriteStartElement("HTML");
htmlWriter.WriteStartElement("BODY");
}
WriteFormattingProperties(xamlReader, htmlWriter, inlineStyle);
WriteElementContent(xamlReader, htmlWriter, inlineStyle);
if (asFullDocument)
{
htmlWriter.WriteEndElement();
htmlWriter.WriteEndElement();
}
return true;
}
/// <summary>
/// Reads attributes of the current xaml element and converts
/// them into appropriate html attributes or css styles.
/// </summary>
/// <param name="xamlReader">
/// XmlTextReader which is expected to be at XmlNodeType.Element
/// (opening element tag) position.
/// The reader will remain at the same level after function complete.
/// </param>
/// <param name="htmlWriter">
/// XmlTextWriter for output html, which is expected to be in
/// after WriteStartElement state.
/// </param>
/// <param name="inlineStyle">
/// String builder for collecting css properties for inline STYLE attribute.
/// </param>
private void WriteFormattingProperties(XmlTextReader xamlReader, XmlTextWriter htmlWriter, StringBuilder inlineStyle)
{
// Clear string builder for the inline style
inlineStyle.Remove(0, inlineStyle.Length);
if (!xamlReader.HasAttributes)
{
return;
}
bool borderSet = false;
while (xamlReader.MoveToNextAttribute())
{
string css = null;
switch (xamlReader.Name)
{
// Character fomatting properties
// ------------------------------
case "Background":
css = "background-color:" + ParseXamlColor(xamlReader.Value) + ";";
break;
case "FontFamily":
css = "font-family:" + xamlReader.Value + ";";
break;
case "FontStyle":
css = "font-style:" + xamlReader.Value.ToLower() + ";";
break;
case "FontWeight":
css = "font-weight:" + xamlReader.Value.ToLower() + ";";
break;
case "FontStretch":
break;
case "FontSize":
css = "font-size:" + xamlReader.Value + "px;";
break;
case "Foreground":
css = "color:" + ParseXamlColor(xamlReader.Value) + ";";
break;
case "TextDecorations":
if (xamlReader.Value.ToLower() == "strikethrough")
css = "text-decoration:line-through;";
else
css = "text-decoration:underline;";
break;
case "TextEffects":
break;
case "Emphasis":
break;
case "StandardLigatures":
break;
case "Variants":
break;
case "Capitals":
break;
case "Fraction":
break;
// Paragraph formatting properties
// -------------------------------
case "Padding":
css = "padding:" + ParseXamlThickness(xamlReader.Value) + ";";
break;
case "Margin":
css = "margin:" + ParseXamlThickness(xamlReader.Value) + ";";
break;
case "BorderThickness":
css = "border-width:" + ParseXamlThickness(xamlReader.Value) + ";";
borderSet = true;
break;
case "BorderBrush":
css = "border-color:" + ParseXamlColor(xamlReader.Value) + ";";
borderSet = true;
break;
case "LineHeight":
break;
case "TextIndent":
css = "text-indent:" + xamlReader.Value + ";";
break;
case "TextAlignment":
css = "text-align:" + xamlReader.Value + ";";
break;
case "IsKeptTogether":
break;
case "IsKeptWithNext":
break;
case "ColumnBreakBefore":
break;
case "PageBreakBefore":
break;
case "FlowDirection":
break;
// Table attributes
// ----------------
case "Width":
css = "width:" + xamlReader.Value + ";";
break;
case "ColumnSpan":
htmlWriter.WriteAttributeString("COLSPAN", xamlReader.Value);
break;
case "RowSpan":
htmlWriter.WriteAttributeString("ROWSPAN", xamlReader.Value);
break;
// Hyperlink Attributes
case "NavigateUri":
htmlWriter.WriteAttributeString("HREF", xamlReader.Value);
break;
case "TargetName":
htmlWriter.WriteAttributeString("TARGET", xamlReader.Value);
break;
}
if (css != null)
{
inlineStyle.Append(css);
}
}
if (borderSet)
{
inlineStyle.Append("border-style:solid;mso-element:para-border-div;");
}
// Return the xamlReader back to element level
xamlReader.MoveToElement();
}
private string ParseXamlColor(string color)
{
if (color.StartsWith("#"))
{
// Remove transparancy value
color = "#" + color.Substring(3);
}
return color;
}
private string ParseXamlThickness(string thickness)
{
string[] values = thickness.Split(',');
for (int i = 0; i < values.Length; i++)
{
if (double.TryParse(values[i], out double value))
{
values[i] = Math.Ceiling(value).ToString();
}
else
{
values[i] = "1";
}
}
switch (values.Length)
{
case 1:
return thickness;
case 2:
return values[1] + " " + values[0];
case 4:
return values[1] + " " + values[2] + " " + values[3] + " " + values[0];
default:
return values[0];
}
}
/// <summary>
/// Reads a content of current xaml element, converts it
/// </summary>
/// <param name="xamlReader">
/// XmlTextReader which is expected to be at XmlNodeType.Element
/// (opening element tag) position.
/// </param>
/// <param name="htmlWriter">
/// May be null, in which case we are skipping the xaml element;
/// witout producing any output to html.
/// </param>
/// <param name="inlineStyle">
/// StringBuilder used for collecting css properties for inline STYLE attribute.
/// </param>
private void WriteElementContent(XmlTextReader xamlReader, XmlTextWriter htmlWriter, StringBuilder inlineStyle)
{
bool elementContentStarted = false;
if (xamlReader.IsEmptyElement)
{
if (htmlWriter != null && !elementContentStarted && inlineStyle.Length > 0)
{
// Output STYLE attribute and clear inlineStyle buffer.
htmlWriter.WriteAttributeString("STYLE", inlineStyle.ToString());
inlineStyle.Remove(0, inlineStyle.Length);
}
elementContentStarted = true;
}
else
{
while (ReadNextToken(xamlReader) && xamlReader.NodeType != XmlNodeType.EndElement)
{
switch (xamlReader.NodeType)
{
case XmlNodeType.Element:
if (xamlReader.Name.Contains("."))
{
AddComplexProperty(xamlReader, inlineStyle);
}
else
{
if (htmlWriter != null && !elementContentStarted && inlineStyle.Length > 0)
{
// Output STYLE attribute and clear inlineStyle buffer.
htmlWriter.WriteAttributeString("STYLE", inlineStyle.ToString());
inlineStyle.Remove(0, inlineStyle.Length);
}
elementContentStarted = true;
WriteElement(xamlReader, htmlWriter, inlineStyle);
}
Debug.Assert(xamlReader.NodeType == XmlNodeType.EndElement || xamlReader.NodeType == XmlNodeType.Element && xamlReader.IsEmptyElement);
break;
case XmlNodeType.Comment:
if (htmlWriter != null)
{
if (!elementContentStarted && inlineStyle.Length > 0)
{
htmlWriter.WriteAttributeString("STYLE", inlineStyle.ToString());
}
htmlWriter.WriteComment(xamlReader.Value);
}
elementContentStarted = true;
break;
case XmlNodeType.CDATA:
case XmlNodeType.Text:
case XmlNodeType.SignificantWhitespace:
if (htmlWriter != null)
{
if (!elementContentStarted && inlineStyle.Length > 0)
{
htmlWriter.WriteAttributeString("STYLE", inlineStyle.ToString());
}
htmlWriter.WriteString(xamlReader.Value);
}
elementContentStarted = true;
break;
}
}
}
}
/// <summary>
/// Conberts an element notation of complex property into
/// </summary>
/// <param name="xamlReader">
/// On entry this XmlTextReader must be on Element start tag;
/// on exit - on EndElement tag.
/// </param>
/// <param name="inlineStyle">
/// StringBuilder containing a value for STYLE attribute.
/// </param>
private void AddComplexProperty(XmlTextReader xamlReader, StringBuilder inlineStyle)
{
if (inlineStyle != null && xamlReader.Name.EndsWith(".TextDecorations"))
{
inlineStyle.Append("text-decoration:underline;");
}
// Skip the element representing the complex property
WriteElementContent(xamlReader, /*htmlWriter:*/null, /*inlineStyle:*/null);
}
/// <summary>
/// Converts a xaml element into an appropriate html element.
/// </summary>
/// <param name="xamlReader">
/// On entry this XmlTextReader must be on Element start tag;
/// on exit - on EndElement tag.
/// </param>
/// <param name="htmlWriter">
/// May be null, in which case we are skipping xaml content
/// without producing any html output
/// </param>
/// <param name="inlineStyle">
/// StringBuilder used for collecting css properties for inline STYLE attributes on every level.
/// </param>
private void WriteElement(XmlTextReader xamlReader, XmlTextWriter htmlWriter, StringBuilder inlineStyle)
{
if (htmlWriter == null)
{
// Skipping mode; recurse into the xaml element without any output
WriteElementContent(xamlReader, /*htmlWriter:*/null, null);
}
else
{
string htmlElementName;
switch (xamlReader.Name)
{
case "Run" :
case "Span":
case "InlineUIContainer":
htmlElementName = "SPAN";
break;
case "Bold":
htmlElementName = "B";
break;
case "Italic" :
htmlElementName = "I";
break;
case "Paragraph" :
htmlElementName = "P";
break;
case "BlockUIContainer":
case "Section":
htmlElementName = "DIV";
break;
case "Table":
htmlElementName = "TABLE";
break;
case "TableColumn":
htmlElementName = "COL";
break;
case "TableRowGroup" :
htmlElementName = "TBODY";
break;
case "TableRow" :
htmlElementName = "TR";
break;
case "TableCell" :
htmlElementName = "TD";
break;
case "List" :
string marker = xamlReader.GetAttribute("MarkerStyle");
if (marker == null || marker == "None" || marker == "Disc" || marker == "Circle" || marker == "Square" || marker == "Box")
{
htmlElementName = "UL";
}
else
{
htmlElementName = "OL";
}
break;
case "ListItem" :
htmlElementName = "LI";
break;
case "Hyperlink":
htmlElementName = "A";
break;
default :
htmlElementName = null; // Ignore the element
break;
}
if (htmlWriter != null && htmlElementName != null)
{
htmlWriter.WriteStartElement(htmlElementName);
WriteFormattingProperties(xamlReader, htmlWriter, inlineStyle);
WriteElementContent(xamlReader, htmlWriter, inlineStyle);
htmlWriter.WriteEndElement();
}
else
{
// Skip this unrecognized xaml element
WriteElementContent(xamlReader, /*htmlWriter:*/null, null);
}
}
}
// Reader advance helpers
// ----------------------
/// <summary>
/// Reads several items from xamlReader skipping all non-significant stuff.
/// </summary>
/// <param name="xamlReader">
/// XmlTextReader from tokens are being read.
/// </param>
/// <returns>
/// True if new token is available; false if end of stream reached.
/// </returns>
private bool ReadNextToken(XmlReader xamlReader)
{
while (xamlReader.Read())
{
switch (xamlReader.NodeType)
{
case XmlNodeType.Element:
case XmlNodeType.EndElement:
case XmlNodeType.None:
case XmlNodeType.CDATA:
case XmlNodeType.Text:
case XmlNodeType.SignificantWhitespace:
return true;
case XmlNodeType.Whitespace:
if (xamlReader.XmlSpace == XmlSpace.Preserve)
{
return true;
}
// ignore insignificant whitespace
break;
case XmlNodeType.EndEntity:
case XmlNodeType.EntityReference:
// Implement entity reading
//xamlReader.ResolveEntity();
//xamlReader.Read();
//ReadChildNodes( parent, parentBaseUri, xamlReader, positionInfo);
break; // for now we ignore entities as insignificant stuff
case XmlNodeType.Comment:
return true;
case XmlNodeType.ProcessingInstruction:
case XmlNodeType.DocumentType:
case XmlNodeType.XmlDeclaration:
default:
// Ignorable stuff
break;
}
}
return false;
}
#endregion Private Methods
}
}
This dll is used by a Windows Service and ConvertRtfToHtml method is called many times.
This is windbg informations :
0:016> !sos.clrstack
OS Thread Id: 0x220c (16)
Child SP IP Call Site
127beb0c 755bc232 [GCFrame: 127beb0c]
127bebcc 755bc232 [HelperMethodFrame_2OBJ: 127bebcc] System.Environment.GetResourceFromDefault(System.String)
127bec50 10fa493c System.Environment.GetResourceString(System.String, System.Object[])
127bec60 10fa48af System.Exception.get_Message()
127bec70 069077d9 *** WARNING: Unable to verify checksum for Convertisseur.dll
SQWebContributeur.Convertisseur.Convert.ConvertRtfInSTAThread(System.Object) [D:\SOLU-QIQ\Projets SVN\DLL Maison\Convertisseur\Convertisseur\Convert.cs # 268]
127bed94 069052d4 System.Threading.ThreadHelper.ThreadStart_Context(System.Object)
127beda0 063e2c17 System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean)
127bee10 063e2177 System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean)
127bee24 06905162 System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object)
127bee3c 069050e3 System.Threading.ThreadHelper.ThreadStart(System.Object)
127bef80 730eebf6 [GCFrame: 127bef80]
127bf164 730eebf6 [DebuggerU2MCatchHandlerFrame: 127bf164]
0:016> !pe -nested
Exception object: 019bbfc0
Exception type: System.Runtime.InteropServices.COMException
Message: Espace insuffisant pour traiter cette commande. (Exception de HRESULT : 0x80070008)
InnerException: <none>
StackTrace (generated):
SP IP Function
00000000 00000001 UNKNOWN!System.Environment.GetResourceFromDefault(System.String)+0x2
127BEC50 10FA493C UNKNOWN!System.Environment.GetResourceString(System.String, System.Object[])+0xc
127BEC60 10FA48AF UNKNOWN!System.Exception.get_Message()+0x4f
127BEC70 069077D9 Convertisseur_ae70000!SQWebContributeur.Convertisseur.Convert.ConvertRtfInSTAThread(System.Object)+0xe9
127BED94 069052D4 UNKNOWN!System.Threading.ThreadHelper.ThreadStart_Context(System.Object)+0x9c
127BEDA0 063E2C17 UNKNOWN!System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean)+0x107
127BEE10 063E2177 UNKNOWN!System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean)+0x17
127BEE24 06905162 UNKNOWN!System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object)+0x3a
127BEE3C 069050E3 UNKNOWN!System.Threading.ThreadHelper.ThreadStart(System.Object)+0x4b
StackTraceString: <none>
HResult: 80070008
Nested exception -------------------------------------------------------------
Exception object: 019b9dc4
Exception type: System.OutOfMemoryException
Message: <none>
InnerException: <none>
StackTrace (generated):
SP IP Function
00000000 00000001 UNKNOWN!System.GC._WaitForPendingFinalizers()+0x2
127BEB68 10FA11DF UNKNOWN!System.GC.WaitForPendingFinalizers()+0x4f
127BEB98 0C55631D Convertisseur_ae70000!SQWebContributeur.ClassesHelp.RtfToHtmlConverter.ConvertRtfToXaml(System.String)+0x385
127BED00 069078C9 Convertisseur_ae70000!SQWebContributeur.ClassesHelp.RtfToHtmlConverter.ConvertRtfToHtml(System.String)+0x51
127BED38 0690779C Convertisseur_ae70000!SQWebContributeur.Convertisseur.Convert.ConvertRtfInSTAThread(System.Object)+0xac
StackTraceString: <none>
HResult: 8007000e
!eeheap -gc command show that 4 Mo are used by garbage collector :
0:016> !eeheap -gc
Number of GC Heaps: 1
generation 0 starts at 0x019b9db8
generation 1 starts at 0x019b9cec
generation 2 starts at 0x01861000
ephemeral segment allocation context: none
segment begin allocated size
01860000 01861000 01bec808 0x38b808(3717128)
Large object heap starts at 0x02861000
segment begin allocated size
02860000 02861000 028ba260 0x59260(365152)
Total Size: Size: 0x3e4a68 (4082280) bytes.
------------------------------
GC Heap Size: Size: 0x3e4a68 (4082280) bytes.
!dumpheap -stat command shows that only 2 Mo are free :
00e6a430 865 2382762 Free
Here is perfmon data :
I don't know what to do to resolve this exception. I try to add GC.Collect() to force GC without any effect.
The VM have 8 Go of physical memory and on another VM with 4 Go, exception not occurs. I don't know how can I resolve this exception.
Thanks for your help
First of all, you did a good job extracting all the inner exceptions in order to figure out that this crash is caused by a OOM exception. Not all developers have that skill.
!eeheap -gc command show that 4 Mo are used by garbage collector
That's correct - and a strong indicator that garbage collecting on its own will not really help. Even if it could free the 4 MB, you have gained almost nothing.
(But: more on that later)
!dumpheap -stat command shows that only 2 Mo are free
While this statement is not wrong, it's also not complete.
a) there are 2 MB free, but those 2 MB are split into 865 different regions. So it might still be impossible to allocate a single 2 MB block
b) these 2 MB are free from .NET point of view. If .NET does not have enough memory free, it will request more memory from the operating system. That request may then succeed or fail, depending on how much memory the OS is available to give.
With that knowledge in mind, you need to ask
why wasn't the operating system able to give .NET more memory?
The reason likely is: because it has given all memory away already. In a 32 bit process, that's 2 GB, 3 GB or 4 GB, depending on configurations and settings (mainly Large Address Aware). That's not much, and especially, it's not available as a contiguous block. In many cases, you only have 700 MB.
Where could the OS have given the memory? To COM objects in your case (because we have a COM exception, but that may be misleading). And these COM objects seem to be native (otherwise they would allocate managed memory). Looking at .NET memory will not help then.
However, there is one exception: if your .NET code is the reason that COM objects are not freed, then your .NET code is indirectly responsible for the native memory leak. So, what you should be looking for is the amount of RCW objects. If you have a lot of them, you need to somehow get rid of them.
If that's not the reason, maybe your RTF is simply too large and does not fit into the largest region of free memory.
I have once made up a graph for tackling down OOM exceptions that tells you where to start.
With !address -summary you take a look at the point of view of the operating system.
You likely have a small <unknown> value, since the .NET usage is small.
If Heap has a large value, the memory is gone via the Windows Heap Manager (e.g. C++) and you have a native leak (possibly caused by COM objects not being released).
You can also have a look at the section "largest region by size", where you'll find a value for Free. That's the maximum value someone can get with a single request. Maybe it's not enough to fit your data.

C# + CoDeSys Automation Platform SDK -> PLC's variable = VarRefState.NotMonitoredYet

I'm trying to read the variable of the PLC.
In the Automation Platform, I have a plugin who start a my test (that I've write in C#).
When I execute the plugin for the first time, it always give me the same error. But if a execute it again it's good.
I use a List<IOnlineVarRef6> vars to read my variable. My error is that my vars's State is NotMonitoredYet.
Exemple :
private bool CompareValues(TestCase test, List<IOnlineVarRef6> vars)
{
// Stop here the first time
if (vars.First().State == VarRefState.NotMonitoredYet)
return false;
// Execute well the other times
List<Variable> initialVars = test.initialVariables;
for (int i = 0; i < vars.Count(); i++)
{
object initialValue = initialVars.Single(v => v.Name == vars[i].Expression.ToString()).Value;
if (!vars[i].Value.Equals(initialValue))
return false;
}
return true;
}
I think the problem is in the method who get my variable :
/// <summary>
/// Create a variable watch for each of the specified variable in the list
/// </summary>
/// <example>
/// Variable should include object hierarchy up to Device
/// GPX.Diesel_Control.CTRL.PB_CRANK1
/// </example>
public List<IOnlineVarRef6> CreateVariableWatch(List<string> vars)
{
IVarRef2 varref;
IOnlineVarRef6 iov;
IOnlineApplication17 onlineapp = (IOnlineApplication17)onlineMgr.GetApplication(SystemInstances.Engine.Projects.PrimaryProject.ActiveApplication);
List<IOnlineVarRef6> lstVarRef = new List<IOnlineVarRef6>();
foreach (string var in vars)
{
varref = (IVarRef2)SystemInstances.LanguageModelMgr.GetVarReference(var);
iov = (IOnlineVarRef6)onlineMgr.CreateWatch(varref);
lstVarRef.Add(iov);
}
return lstVarRef;
}
I have a method that wait before calling the CompareValues() and it retries 3 times and it wait before trying again :
public void SetIsTestPassed(TestCase test)
{
Thread.Sleep(test.delayedStart);
int retries = test.retries;
do
{
List<IOnlineVarRef6> vars = SetCurrentVars(test);
test.IsTestPassed = CompareValues(test, vars);
if (test.retryDelay > 0 && !test.IsTestPassed)
Thread.Sleep(test.retryDelay);
retries--;
} while (retries != 0 && !test.IsTestPassed);
}
private List<IOnlineVarRef6> SetCurrentVars(TestCase test)
{
OnlineManagerHelper OnlineMgr = new OnlineManagerHelper(true);
return OnlineMgr.CreateVariableWatch(
test.initialVariables
.Select(v => Settings.Default.InstancePath + v.Name)
.ToList());
}

Categories