You're breathtaking!
This commit is contained in:
@@ -0,0 +1,36 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
namespace Flax.Build
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="StreamWriter"/> implementation with a custom encoding.
|
||||
/// </summary>
|
||||
/// <seealso cref="System.IO.StringWriter" />
|
||||
public sealed class StringWriterWithEncoding : StringWriter
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="StringWriterWithEncoding"/> class.
|
||||
/// </summary>
|
||||
public StringWriterWithEncoding()
|
||||
: this(Encoding.UTF8)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="StringWriterWithEncoding"/> class.
|
||||
/// </summary>
|
||||
/// <param name="encoding">The encoding.</param>
|
||||
public StringWriterWithEncoding(Encoding encoding)
|
||||
{
|
||||
Encoding = encoding;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="T:System.Text.Encoding" /> in which the output is written.
|
||||
/// </summary>
|
||||
public override Encoding Encoding { get; }
|
||||
}
|
||||
}
|
||||
515
Source/Tools/Flax.Build/Utilities/Tokenizer.cs
Normal file
515
Source/Tools/Flax.Build/Utilities/Tokenizer.cs
Normal file
@@ -0,0 +1,515 @@
|
||||
// Copyright (c) 2012-2019 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Flax.Build
|
||||
{
|
||||
/// <summary>
|
||||
/// Types of the tokens supported by the <see cref="Tokenizer"/>.
|
||||
/// </summary>
|
||||
public enum TokenType
|
||||
{
|
||||
/// <summary>
|
||||
/// A whitespace.
|
||||
/// </summary>
|
||||
Whitespace,
|
||||
|
||||
/// <summary>
|
||||
/// A Newline.
|
||||
/// </summary>
|
||||
Newline,
|
||||
|
||||
/// <summary>
|
||||
/// A multi line comment.
|
||||
/// </summary>
|
||||
CommentMultiLine,
|
||||
|
||||
/// <summary>
|
||||
/// A single line comment.
|
||||
/// </summary>
|
||||
CommentSingleLine,
|
||||
|
||||
/// <summary>
|
||||
/// An identifier.
|
||||
/// </summary>
|
||||
Identifier,
|
||||
|
||||
/// <summary>
|
||||
/// A number in hexadecimal form.
|
||||
/// </summary>
|
||||
Hex,
|
||||
|
||||
/// <summary>
|
||||
/// A number.
|
||||
/// </summary>
|
||||
Number,
|
||||
|
||||
/// <summary>
|
||||
/// The symbol '='.
|
||||
/// </summary>
|
||||
Equal,
|
||||
|
||||
/// <summary>
|
||||
/// A comma ','.
|
||||
/// </summary>
|
||||
Comma,
|
||||
|
||||
/// <summary>
|
||||
/// A Semicolon ';'.
|
||||
/// </summary>
|
||||
SemiColon,
|
||||
|
||||
/// <summary>
|
||||
/// A left curly brace '{'.
|
||||
/// </summary>
|
||||
LeftCurlyBrace,
|
||||
|
||||
/// <summary>
|
||||
/// A right curly brace '}'.
|
||||
/// </summary>
|
||||
RightCurlyBrace,
|
||||
|
||||
/// <summary>
|
||||
/// A left parenthesis '('.
|
||||
/// </summary>
|
||||
LeftParent,
|
||||
|
||||
/// <summary>
|
||||
/// A right parenthesis ')'.
|
||||
/// </summary>
|
||||
RightParent,
|
||||
|
||||
/// <summary>
|
||||
/// A left bracket '['.
|
||||
/// </summary>
|
||||
LeftBracket,
|
||||
|
||||
/// <summary>
|
||||
/// A right bracket ']'.
|
||||
/// </summary>
|
||||
RightBracket,
|
||||
|
||||
/// <summary>
|
||||
/// A text.
|
||||
/// </summary>
|
||||
String,
|
||||
|
||||
/// <summary>
|
||||
/// An character.
|
||||
/// </summary>
|
||||
Character,
|
||||
|
||||
/// <summary>
|
||||
/// A preprocessor token '#'
|
||||
/// </summary>
|
||||
Preprocessor,
|
||||
|
||||
/// <summary>
|
||||
/// A colon ':'.
|
||||
/// </summary>
|
||||
Colon,
|
||||
|
||||
/// <summary>
|
||||
/// A double colon '::'.
|
||||
/// </summary>
|
||||
DoubleColon,
|
||||
|
||||
/// <summary>
|
||||
/// A dot '.'.
|
||||
/// </summary>
|
||||
Dot,
|
||||
|
||||
/// <summary>
|
||||
/// A '<'.
|
||||
/// </summary>
|
||||
LessThan,
|
||||
|
||||
/// <summary>
|
||||
/// A '>'.
|
||||
/// </summary>
|
||||
GreaterThan,
|
||||
|
||||
/// <summary>
|
||||
/// A '&'.
|
||||
/// </summary>
|
||||
And,
|
||||
|
||||
/// <summary>
|
||||
/// A '*'.
|
||||
/// </summary>
|
||||
Multiply,
|
||||
|
||||
/// <summary>
|
||||
/// A '/'.
|
||||
/// </summary>
|
||||
Divide,
|
||||
|
||||
/// <summary>
|
||||
/// A '+'.
|
||||
/// </summary>
|
||||
Add,
|
||||
|
||||
/// <summary>
|
||||
/// A '-'.
|
||||
/// </summary>
|
||||
Sub,
|
||||
|
||||
/// <summary>
|
||||
/// An unknown symbol.
|
||||
/// </summary>
|
||||
Unknown,
|
||||
|
||||
/// <summary>
|
||||
/// A end of file token.
|
||||
/// </summary>
|
||||
EndOfFile,
|
||||
|
||||
/// <summary>
|
||||
/// A '<'.
|
||||
/// </summary>
|
||||
LeftAngleBracket = LessThan,
|
||||
|
||||
/// <summary>
|
||||
/// A '>'.
|
||||
/// </summary>
|
||||
RightAngleBracket = GreaterThan,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Contains information about a token language.
|
||||
/// </summary>
|
||||
public class Token : IEquatable<Token>
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Token"/> class.
|
||||
/// </summary>
|
||||
public Token()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Token" /> struct.
|
||||
/// </summary>
|
||||
/// <param name="type">The type.</param>
|
||||
/// <param name="value">The value.</param>
|
||||
public Token(TokenType type, string value)
|
||||
{
|
||||
Type = type;
|
||||
Value = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The type of the token.
|
||||
/// </summary>
|
||||
public TokenType Type;
|
||||
|
||||
/// <summary>
|
||||
/// Value of the token.
|
||||
/// </summary>
|
||||
public string Value;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("{{{0}:{1}}}", Type, Value);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Equals(Token other)
|
||||
{
|
||||
if (ReferenceEquals(null, other))
|
||||
return false;
|
||||
if (ReferenceEquals(this, other))
|
||||
return true;
|
||||
return Type == other.Type && Value == other.Value;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (ReferenceEquals(null, obj))
|
||||
return false;
|
||||
if (ReferenceEquals(this, obj))
|
||||
return true;
|
||||
if (obj.GetType() != this.GetType())
|
||||
return false;
|
||||
return Equals((Token)obj);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override int GetHashCode()
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
return ((int)Type * 397) ^ (Value != null ? Value.GetHashCode() : 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The tokens parsing utility that implements basic logic for generic C-like syntax source code parsing.
|
||||
/// </summary>
|
||||
public class Tokenizer : IDisposable
|
||||
{
|
||||
private static readonly Regex RegexTokenizer = new Regex
|
||||
(
|
||||
@"(?<ws>[ \t]+)|" +
|
||||
@"(?<nl>(?:\r\n|\n))|" +
|
||||
@"(?<commul>/\*(?:(?!\*/)(?:.|[\r\n]+))*\*/)|" +
|
||||
@"(?<comsin>//(.*?)\r?\n)|" +
|
||||
@"(?<ident>[a-zA-Z_][a-zA-Z0-9_]*)|" +
|
||||
@"(?<hex>0x[0-9a-fA-F]+)|" +
|
||||
@"(?<number>[\-\+]?\s*[0-9]*\.?[0-9]+(?:[eE][-+]?[0-9]+)?f?)|" +
|
||||
@"(?<equal>=)|" +
|
||||
@"(?<comma>,)|" +
|
||||
@"(?<semicolon>;)|" +
|
||||
@"(?<lcb>\{)|" +
|
||||
@"(?<rcb>\})|" +
|
||||
@"(?<lpar>\()|" +
|
||||
@"(?<rpar>\))|" +
|
||||
@"(?<lb>\[)|" +
|
||||
@"(?<rb>\])|" +
|
||||
@"(?<str>""[^""\\]*(?:\\.[^""\\]*)*"")|" +
|
||||
@"(?<char>'[^'\\]*(?:\\.[^'\\]*)*')|" +
|
||||
@"(?<prep>#)|" +
|
||||
@"(?<colon>:)|" +
|
||||
@"(?<doublecolon>::)|" +
|
||||
@"(?<dot>\.)|" +
|
||||
@"(?<lt>\<)|" +
|
||||
@"(?<gt>\>)|" +
|
||||
@"(?<and>\&)|" +
|
||||
@"(?<mul>\*)|" +
|
||||
@"(?<div>\/)|" +
|
||||
@"(?<add>\+)|" +
|
||||
@"(?<sub>\-)|" +
|
||||
@"(?<unk>[^\s]+)",
|
||||
RegexOptions.Compiled
|
||||
);
|
||||
|
||||
private ITwoWayEnumerator<Token> _tokenEnumerator;
|
||||
private int _line = 1;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current token.
|
||||
/// </summary>
|
||||
public Token CurrentToken => _tokenEnumerator.Current;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current line number (starting from number 1).
|
||||
/// </summary>
|
||||
public int CurrentLine => _line;
|
||||
|
||||
/// <summary>
|
||||
/// Tokenizes the given file (through constructor).
|
||||
/// </summary>
|
||||
/// <param name="sourceCode">The source code for this tokenizer to run on.</param>
|
||||
public void Tokenize(string sourceCode)
|
||||
{
|
||||
if (_tokenEnumerator != null)
|
||||
throw new Exception("This code is already parsed!");
|
||||
|
||||
var tokens = TokenizeInternal(sourceCode);
|
||||
_tokenEnumerator = tokens.GetTwoWayEnumerator();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets next token.
|
||||
/// </summary>
|
||||
/// <param name="includeWhitespaces">When false, all white-space tokens will be ignored.</param>
|
||||
/// <param name="includeComments">When false, all comment (single line and multi-line) tokens will be ignored.</param>
|
||||
/// <returns>The token. Check for EndOfFile token-type to detect end-of-file.</returns>
|
||||
public Token NextToken(bool includeWhitespaces = false, bool includeComments = false)
|
||||
{
|
||||
while (_tokenEnumerator.MoveNext())
|
||||
{
|
||||
var token = _tokenEnumerator.Current;
|
||||
if (token == null)
|
||||
continue;
|
||||
|
||||
_line += CountLines(token);
|
||||
|
||||
if (token.Type == TokenType.Newline)
|
||||
{
|
||||
if (includeWhitespaces)
|
||||
return token;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!includeWhitespaces && token.Type == TokenType.Whitespace)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
return token;
|
||||
}
|
||||
|
||||
return new Token(TokenType.EndOfFile, string.Empty);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Moves to the previous the token.
|
||||
/// </summary>
|
||||
/// <param name="includeWhitespaces">If set to <c>true</c> includes whitespaces.</param>
|
||||
/// <param name="includeComments">If set to <c>true</c> include comments.</param>
|
||||
/// <returns>The token. Check for EndOfFile token-type to detect end-of-file.</returns>
|
||||
public Token PreviousToken(bool includeWhitespaces = false, bool includeComments = false)
|
||||
{
|
||||
while (_tokenEnumerator.MovePrevious())
|
||||
{
|
||||
var token = _tokenEnumerator.Current;
|
||||
if (token == null)
|
||||
continue;
|
||||
|
||||
_line -= CountLines(token);
|
||||
|
||||
if (token.Type == TokenType.Newline)
|
||||
{
|
||||
if (includeWhitespaces)
|
||||
return token;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!includeWhitespaces && token.Type == TokenType.Whitespace)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
return token;
|
||||
}
|
||||
|
||||
return new Token(TokenType.EndOfFile, string.Empty);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Expects any token of given types. Throws <see cref="Exception"/> when token is not found.
|
||||
/// </summary>
|
||||
/// <param name="tokenTypes">The allowed token types.</param>
|
||||
/// <param name="includeWhitespaces">When false, all white-space tokens will be ignored.</param>
|
||||
/// <param name="includeComments">When false, all comment (single line and multi-line) tokens will be ignored.</param>
|
||||
/// <returns>The found token.</returns>
|
||||
public Token ExpectAnyTokens(TokenType[] tokenTypes, bool includeWhitespaces = false, bool includeComments = false)
|
||||
{
|
||||
var token = NextToken(includeWhitespaces, includeComments);
|
||||
|
||||
if (tokenTypes.Contains(token.Type))
|
||||
return token;
|
||||
|
||||
throw new Exception($"Expected {string.Join(" or ", tokenTypes)}, but got {token} at line {_line}.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Expects token of given types in the same order. Throws <see cref="Exception"/> when token is not found.
|
||||
/// </summary>
|
||||
/// <param name="tokenTypes">The allowed token types.</param>
|
||||
/// <param name="includeWhitespaces">When false, all white-space tokens will be ignored.</param>
|
||||
/// <param name="includeComments">When false, all comment (single line and multi-line) tokens will be ignored.</param>
|
||||
/// <returns>The found token.</returns>
|
||||
public void ExpectAllTokens(TokenType[] tokenTypes, bool includeWhitespaces = false, bool includeComments = false)
|
||||
{
|
||||
foreach (var tokenType in tokenTypes)
|
||||
{
|
||||
var token = NextToken(includeWhitespaces, includeComments);
|
||||
|
||||
if (token.Type != tokenType)
|
||||
throw new Exception($"Expected {tokenType}, but got {token} at line {_line}.");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Expects any token of given type. Throws <see cref="Exception"/> when token is not found.
|
||||
/// </summary>
|
||||
/// <param name="tokenType">The only allowed token type.</param>
|
||||
/// <param name="includeWhitespaces">When false, all white-space tokens will be ignored.</param>
|
||||
/// <param name="includeComments">When false, all comment (single line and multi-line) tokens will be ignored.</param>
|
||||
/// <returns>The found token.</returns>
|
||||
public Token ExpectToken(TokenType tokenType, bool includeWhitespaces = false, bool includeComments = false)
|
||||
{
|
||||
var token = NextToken(includeWhitespaces, includeComments);
|
||||
|
||||
if (token.Type == tokenType)
|
||||
return token;
|
||||
|
||||
throw new Exception($"Expected {tokenType}, but got {token} at line {_line}.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Skips all tokens until the tokenizer steps into token of given type (and it is also skipped, so, NextToken will give the next token).
|
||||
/// </summary>
|
||||
/// <param name="tokenType">The expected token type.</param>
|
||||
public void SkipUntil(TokenType tokenType)
|
||||
{
|
||||
do
|
||||
{
|
||||
} while (NextToken(true).Type != tokenType);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Skips all tokens until the tokenizer steps into token of given type (and it is also skipped, so, NextToken will give the next token).
|
||||
/// </summary>
|
||||
/// <param name="tokenType">The expected token type.</param>
|
||||
/// <param name="context">The output contents of the skipped tokens.</param>
|
||||
public void SkipUntil(TokenType tokenType, out string context)
|
||||
{
|
||||
context = string.Empty;
|
||||
while (NextToken(true).Type != tokenType)
|
||||
{
|
||||
context += CurrentToken.Value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disposes the <see cref="Tokenizer"/>.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
_tokenEnumerator?.Dispose();
|
||||
}
|
||||
|
||||
private IEnumerable<Token> TokenizeInternal(string input)
|
||||
{
|
||||
var matches = RegexTokenizer.Matches(input);
|
||||
foreach (Match match in matches)
|
||||
{
|
||||
var i = 0;
|
||||
foreach (Group group in match.Groups)
|
||||
{
|
||||
var matchValue = group.Value;
|
||||
|
||||
if (group.Success && i > 1)
|
||||
{
|
||||
yield return new Token
|
||||
{
|
||||
Type = (TokenType)(i - 2),
|
||||
Value = matchValue
|
||||
};
|
||||
}
|
||||
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int CountLines(Token token)
|
||||
{
|
||||
int result = 0;
|
||||
switch (token.Type)
|
||||
{
|
||||
case TokenType.Newline:
|
||||
case TokenType.CommentSingleLine:
|
||||
result = 1;
|
||||
break;
|
||||
case TokenType.CommentMultiLine:
|
||||
for (int i = 0; i < token.Value.Length; i++)
|
||||
{
|
||||
if (token.Value[i] == '\n')
|
||||
result++;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
119
Source/Tools/Flax.Build/Utilities/TwoWayEnumerator.cs
Normal file
119
Source/Tools/Flax.Build/Utilities/TwoWayEnumerator.cs
Normal file
@@ -0,0 +1,119 @@
|
||||
// Copyright (c) 2012-2019 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Flax.Build
|
||||
{
|
||||
/// <summary>
|
||||
/// The two-way enumerator interface that can move forward or backwards.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The element type.</typeparam>
|
||||
/// <seealso cref="System.Collections.Generic.IEnumerator{T}" />
|
||||
public interface ITwoWayEnumerator<T> : IEnumerator<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// Advances the enumerator to the previous element of the collection.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// <see langword="true" /> if the enumerator was successfully advanced to the previous element; <see langword="false" /> if the enumerator has passed the beginning of the collection.</returns>
|
||||
/// <exception cref="T:System.InvalidOperationException">The collection was modified after the enumerator was created. </exception>
|
||||
bool MovePrevious();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The implementation of the <see cref="ITwoWayEnumerator{T}"/> that uses a list.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The element type.</typeparam>
|
||||
/// <seealso cref="Flax.Build.ITwoWayEnumerator{T}" />
|
||||
public class TwoWayEnumerator<T> : ITwoWayEnumerator<T>
|
||||
{
|
||||
private IEnumerator<T> _enumerator;
|
||||
private List<T> _buffer;
|
||||
private int _index;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TwoWayEnumerator{T}"/> class.
|
||||
/// </summary>
|
||||
/// <param name="enumerator">The enumerator.</param>
|
||||
public TwoWayEnumerator(IEnumerator<T> enumerator)
|
||||
{
|
||||
_enumerator = enumerator ?? throw new ArgumentNullException("enumerator");
|
||||
_buffer = new List<T>();
|
||||
_index = -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Advances the enumerator to the previous element of the collection.
|
||||
/// </summary>
|
||||
/// <returns><see langword="true" /> if the enumerator was successfully advanced to the previous element; <see langword="false" /> if the enumerator has passed the beginning of the collection.</returns>
|
||||
public bool MovePrevious()
|
||||
{
|
||||
if (_index <= 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
--_index;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Advances the enumerator to the next element of the collection.
|
||||
/// </summary>
|
||||
/// <returns><see langword="true" /> if the enumerator was successfully advanced to the next element; <see langword="false" /> if the enumerator has passed the end of the collection.</returns>
|
||||
public bool MoveNext()
|
||||
{
|
||||
if (_index < _buffer.Count - 1)
|
||||
{
|
||||
++_index;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (_enumerator.MoveNext())
|
||||
{
|
||||
_buffer.Add(_enumerator.Current);
|
||||
++_index;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the element in the collection at the current position of the enumerator.
|
||||
/// </summary>
|
||||
public T Current
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_index < 0 || _index >= _buffer.Count)
|
||||
throw new InvalidOperationException();
|
||||
return _buffer[_index];
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the enumerator to its initial position, which is before the first element in the collection.
|
||||
/// </summary>
|
||||
public void Reset()
|
||||
{
|
||||
_enumerator.Reset();
|
||||
_buffer.Clear();
|
||||
_index = -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
_enumerator.Dispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the element in the collection at the current position of the enumerator.
|
||||
/// </summary>
|
||||
object System.Collections.IEnumerator.Current => Current;
|
||||
}
|
||||
}
|
||||
591
Source/Tools/Flax.Build/Utilities/Utilities.cs
Normal file
591
Source/Tools/Flax.Build/Utilities/Utilities.cs
Normal file
@@ -0,0 +1,591 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Flax.Build
|
||||
{
|
||||
/// <summary>
|
||||
/// The utilities.
|
||||
/// </summary>
|
||||
public static class Utilities
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the size of the file as a readable string.
|
||||
/// </summary>
|
||||
/// <param name="path">The path.</param>
|
||||
/// <returns>The file size text.</returns>
|
||||
public static string GetFileSize(string path)
|
||||
{
|
||||
return (Math.Floor(new FileInfo(path).Length / 1024.0f / 1024 * 100) / 100) + " MB";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the range of the items to the hash set.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The item type.</typeparam>
|
||||
/// <param name="source">The hash set to modify.</param>
|
||||
/// <param name="items">The items collection to append.</param>
|
||||
public static void AddRange<T>(this HashSet<T> source, IEnumerable<T> items)
|
||||
{
|
||||
foreach (T item in items)
|
||||
{
|
||||
source.Add(item);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the two way enumerator for the given enumerable collection.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The item type.</typeparam>
|
||||
/// <param name="source">The source collection.</param>
|
||||
/// <returns>The enumerator.</returns>
|
||||
public static ITwoWayEnumerator<T> GetTwoWayEnumerator<T>(this IEnumerable<T> source)
|
||||
{
|
||||
if (source == null)
|
||||
throw new ArgumentNullException(nameof(source));
|
||||
return new TwoWayEnumerator<T>(source.GetEnumerator());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies the file.
|
||||
/// </summary>
|
||||
/// <param name="srcFilePath">The source file path.</param>
|
||||
/// <param name="dstFilePath">The destination file path.</param>
|
||||
/// <param name="overwrite"><see langword="true" /> if the destination file can be overwritten; otherwise, <see langword="false" />.</param>
|
||||
public static void FileCopy(string srcFilePath, string dstFilePath, bool overwrite = true)
|
||||
{
|
||||
if (string.IsNullOrEmpty(srcFilePath))
|
||||
throw new ArgumentNullException(nameof(srcFilePath));
|
||||
if (string.IsNullOrEmpty(dstFilePath))
|
||||
throw new ArgumentNullException(nameof(dstFilePath));
|
||||
|
||||
Log.Verbose(srcFilePath + " -> " + dstFilePath);
|
||||
|
||||
try
|
||||
{
|
||||
File.Copy(srcFilePath, dstFilePath, overwrite);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (!File.Exists(srcFilePath))
|
||||
Log.Error("Failed to copy file. Missing source file.");
|
||||
else
|
||||
Log.Error("Failed to copy file. " + ex.Message);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies the directories.
|
||||
/// </summary>
|
||||
/// <param name="srcDirectoryPath">The source directory path.</param>
|
||||
/// <param name="dstDirectoryPath">The destination directory path.</param>
|
||||
/// <param name="copySubdirs">If set to <c>true</c> copy sub-directories (recursive copy operation).</param>
|
||||
/// <param name="overrideFiles">If set to <c>true</c> override target files if any is existing.</param>
|
||||
/// <param name="searchPattern">The custom filter for the filenames to copy. Can be used to select files to copy. Null if unused.</param>
|
||||
public static void DirectoryCopy(string srcDirectoryPath, string dstDirectoryPath, bool copySubdirs = true, bool overrideFiles = false, string searchPattern = null)
|
||||
{
|
||||
Log.Verbose(srcDirectoryPath + " -> " + dstDirectoryPath);
|
||||
|
||||
var dir = new DirectoryInfo(srcDirectoryPath);
|
||||
|
||||
if (!dir.Exists)
|
||||
{
|
||||
throw new DirectoryNotFoundException("Missing source directory to copy. " + srcDirectoryPath);
|
||||
}
|
||||
|
||||
if (!Directory.Exists(dstDirectoryPath))
|
||||
{
|
||||
Directory.CreateDirectory(dstDirectoryPath);
|
||||
}
|
||||
|
||||
var files = searchPattern != null ? dir.GetFiles(searchPattern) : dir.GetFiles();
|
||||
for (int i = 0; i < files.Length; i++)
|
||||
{
|
||||
string tmp = Path.Combine(dstDirectoryPath, files[i].Name);
|
||||
File.Copy(files[i].FullName, tmp, overrideFiles);
|
||||
}
|
||||
|
||||
if (copySubdirs)
|
||||
{
|
||||
var dirs = dir.GetDirectories();
|
||||
for (int i = 0; i < dirs.Length; i++)
|
||||
{
|
||||
string tmp = Path.Combine(dstDirectoryPath, dirs[i].Name);
|
||||
DirectoryCopy(dirs[i].FullName, tmp, true, overrideFiles, searchPattern);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void DirectoryDelete(DirectoryInfo dir)
|
||||
{
|
||||
var subdirs = dir.GetDirectories();
|
||||
foreach (var subdir in subdirs)
|
||||
{
|
||||
DirectoryDelete(subdir);
|
||||
}
|
||||
|
||||
var files = dir.GetFiles();
|
||||
foreach (var file in files)
|
||||
{
|
||||
try
|
||||
{
|
||||
file.Delete();
|
||||
}
|
||||
catch (UnauthorizedAccessException)
|
||||
{
|
||||
File.SetAttributes(file.FullName, FileAttributes.Normal);
|
||||
file.Delete();
|
||||
}
|
||||
}
|
||||
|
||||
dir.Delete();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deletes the directory.
|
||||
/// </summary>
|
||||
/// <param name="directoryPath">The directory path.</param>
|
||||
/// <param name="withSubdirs">if set to <c>true</c> with sub-directories (recursive delete operation).</param>
|
||||
public static void DirectoryDelete(string directoryPath, bool withSubdirs = true)
|
||||
{
|
||||
var dir = new DirectoryInfo(directoryPath);
|
||||
|
||||
if (!dir.Exists)
|
||||
return;
|
||||
|
||||
if (withSubdirs)
|
||||
DirectoryDelete(dir);
|
||||
else
|
||||
dir.Delete();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The process run options.
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum RunOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// The none.
|
||||
/// </summary>
|
||||
None = 0,
|
||||
|
||||
/// <summary>
|
||||
/// The application must exist.
|
||||
/// </summary>
|
||||
AppMustExist = 1 << 0,
|
||||
|
||||
/// <summary>
|
||||
/// Skip waiting for exit.
|
||||
/// </summary>
|
||||
NoWaitForExit = 1 << 1,
|
||||
|
||||
/// <summary>
|
||||
/// Skip standard output redirection to log.
|
||||
/// </summary>
|
||||
NoStdOutRedirect = 1 << 2,
|
||||
|
||||
/// <summary>
|
||||
/// Skip logging of command run.
|
||||
/// </summary>
|
||||
NoLoggingOfRunCommand = 1 << 3,
|
||||
|
||||
/// <summary>
|
||||
/// Uses UTF-8 output encoding format.
|
||||
/// </summary>
|
||||
UTF8Output = 1 << 4,
|
||||
|
||||
/// <summary>
|
||||
/// Skips logging of run duration.
|
||||
/// </summary>
|
||||
NoLoggingOfRunDuration = 1 << 5,
|
||||
|
||||
/// <summary>
|
||||
/// The default options.
|
||||
/// </summary>
|
||||
Default = AppMustExist,
|
||||
}
|
||||
|
||||
private static void StdOut(object sender, DataReceivedEventArgs e)
|
||||
{
|
||||
if (e.Data != null)
|
||||
{
|
||||
Log.Verbose(e.Data);
|
||||
}
|
||||
}
|
||||
|
||||
private static void StdErr(object sender, DataReceivedEventArgs e)
|
||||
{
|
||||
if (e.Data != null)
|
||||
{
|
||||
Log.Verbose(e.Data);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Runs the external program.
|
||||
/// </summary>
|
||||
/// <param name="app">Program filename.</param>
|
||||
/// <param name="commandLine">Commandline</param>
|
||||
/// <param name="input">Optional Input for the program (will be provided as stdin)</param>
|
||||
/// <param name="workspace">Optional custom workspace directory. Use null to keep the same directory.</param>
|
||||
/// <param name="options">Defines the options how to run. See RunOptions.</param>
|
||||
/// <param name="envVars">Custom environment variables to pass to the child process.</param>
|
||||
/// <returns>The exit code of the program.</returns>
|
||||
public static int Run(string app, string commandLine = null, string input = null, string workspace = null, RunOptions options = RunOptions.Default, Dictionary<string, string> envVars = null)
|
||||
{
|
||||
// Check if the application exists, including the PATH directories.
|
||||
if (options.HasFlag(RunOptions.AppMustExist) && !File.Exists(app))
|
||||
{
|
||||
bool existsInPath = false;
|
||||
if (!app.Contains(Path.DirectorySeparatorChar) && !app.Contains(Path.AltDirectorySeparatorChar))
|
||||
{
|
||||
string[] pathDirectories = Environment.GetEnvironmentVariable("PATH").Split(Path.PathSeparator);
|
||||
foreach (string pathDirectory in pathDirectories)
|
||||
{
|
||||
string tryApp = Path.Combine(pathDirectory, app);
|
||||
if (File.Exists(tryApp))
|
||||
{
|
||||
app = tryApp;
|
||||
existsInPath = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!existsInPath)
|
||||
{
|
||||
throw new Exception(string.Format("Couldn't find the executable to run: {0}", app));
|
||||
}
|
||||
}
|
||||
|
||||
var startTime = DateTime.UtcNow;
|
||||
if (!options.HasFlag(RunOptions.NoLoggingOfRunCommand))
|
||||
{
|
||||
Log.Verbose("Running: " + app + " " + (string.IsNullOrEmpty(commandLine) ? "" : commandLine));
|
||||
}
|
||||
|
||||
bool redirectStdOut = (options & RunOptions.NoStdOutRedirect) != RunOptions.NoStdOutRedirect;
|
||||
|
||||
Process proc = new Process();
|
||||
proc.StartInfo.FileName = app;
|
||||
proc.StartInfo.Arguments = string.IsNullOrEmpty(commandLine) ? "" : commandLine;
|
||||
proc.StartInfo.UseShellExecute = false;
|
||||
proc.StartInfo.RedirectStandardInput = input != null;
|
||||
proc.StartInfo.CreateNoWindow = true;
|
||||
|
||||
if (workspace != null)
|
||||
{
|
||||
proc.StartInfo.WorkingDirectory = workspace;
|
||||
}
|
||||
|
||||
if (redirectStdOut)
|
||||
{
|
||||
proc.StartInfo.RedirectStandardOutput = true;
|
||||
proc.StartInfo.RedirectStandardError = true;
|
||||
proc.OutputDataReceived += StdOut;
|
||||
proc.ErrorDataReceived += StdErr;
|
||||
}
|
||||
|
||||
if (envVars != null)
|
||||
{
|
||||
foreach (var env in envVars)
|
||||
{
|
||||
if (env.Key == "PATH")
|
||||
{
|
||||
proc.StartInfo.EnvironmentVariables[env.Key] = proc.StartInfo.EnvironmentVariables[env.Key] + ';' + env.Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
proc.StartInfo.EnvironmentVariables[env.Key] = env.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ((options & RunOptions.UTF8Output) == RunOptions.UTF8Output)
|
||||
{
|
||||
proc.StartInfo.StandardOutputEncoding = new UTF8Encoding(false, false);
|
||||
}
|
||||
|
||||
proc.Start();
|
||||
|
||||
if (redirectStdOut)
|
||||
{
|
||||
proc.BeginOutputReadLine();
|
||||
proc.BeginErrorReadLine();
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(input) == false)
|
||||
{
|
||||
proc.StandardInput.WriteLine(input);
|
||||
proc.StandardInput.Close();
|
||||
}
|
||||
|
||||
if (!options.HasFlag(RunOptions.NoWaitForExit))
|
||||
{
|
||||
proc.WaitForExit();
|
||||
}
|
||||
|
||||
int result = -1;
|
||||
|
||||
if (!options.HasFlag(RunOptions.NoWaitForExit))
|
||||
{
|
||||
var buildDuration = (DateTime.UtcNow - startTime).TotalMilliseconds;
|
||||
result = proc.ExitCode;
|
||||
if (!options.HasFlag(RunOptions.NoLoggingOfRunCommand) || options.HasFlag(RunOptions.NoLoggingOfRunDuration))
|
||||
{
|
||||
Log.Info(string.Format("Took {0}s to run {1}, ExitCode={2}", buildDuration / 1000, Path.GetFileName(app), result));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a relative path from the given base directory.
|
||||
/// </summary>
|
||||
/// <param name="path">The source path to convert from absolute into a relative format.</param>
|
||||
/// <param name="directory">The directory to create a relative path from.</param>
|
||||
/// <returns>Thew relative path from the given directory.</returns>
|
||||
public static string MakePathRelativeTo(string path, string directory)
|
||||
{
|
||||
// Find how much of the path is common between the two paths. This length does not include a trailing directory separator character
|
||||
int commonDirectoryLength = -1;
|
||||
for (int i = 0;; i++)
|
||||
{
|
||||
if (i == path.Length)
|
||||
{
|
||||
// Check if two paths are the same
|
||||
if (i == directory.Length)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
// Check if we're finishing on a complete directory name
|
||||
if (directory[i] == Path.DirectorySeparatorChar)
|
||||
{
|
||||
commonDirectoryLength = i;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (i == directory.Length)
|
||||
{
|
||||
// Check whether the end of the directory name coincides with a boundary for the current name
|
||||
if (path[i] == Path.DirectorySeparatorChar)
|
||||
{
|
||||
commonDirectoryLength = i;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
// Check the two paths match, and bail if they don't. Increase the common directory length if we've reached a separator
|
||||
if (string.Compare(path, i, directory, i, 1, StringComparison.OrdinalIgnoreCase) != 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (path[i] == Path.DirectorySeparatorChar)
|
||||
{
|
||||
commonDirectoryLength = i;
|
||||
}
|
||||
}
|
||||
|
||||
// If there's no relative path, just return the absolute path
|
||||
if (commonDirectoryLength == -1)
|
||||
{
|
||||
return path;
|
||||
}
|
||||
|
||||
// Append all the '..' separators to get back to the common directory, then the rest of the string to reach the target item
|
||||
StringBuilder result = new StringBuilder();
|
||||
for (int i = commonDirectoryLength + 1; i < directory.Length; i++)
|
||||
{
|
||||
// Move up a directory
|
||||
result.Append("..");
|
||||
result.Append(Path.DirectorySeparatorChar);
|
||||
|
||||
// Scan to the next directory separator
|
||||
while (i < directory.Length && directory[i] != Path.DirectorySeparatorChar)
|
||||
{
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
if (commonDirectoryLength + 1 < path.Length)
|
||||
{
|
||||
result.Append(path, commonDirectoryLength + 1, path.Length - commonDirectoryLength - 1);
|
||||
}
|
||||
|
||||
return result.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Normalizes the path to the standard Flax format (all separators are '/' except for drive 'C:\').
|
||||
/// </summary>
|
||||
/// <param name="path">The path.</param>
|
||||
/// <returns>The normalized path.</returns>
|
||||
public static string NormalizePath(string path)
|
||||
{
|
||||
if (string.IsNullOrEmpty(path))
|
||||
return path;
|
||||
var chars = path.ToCharArray();
|
||||
|
||||
// Convert all '\' to '/'
|
||||
for (int i = 0; i < chars.Length; i++)
|
||||
{
|
||||
if (chars[i] == '\\')
|
||||
chars[i] = '/';
|
||||
}
|
||||
|
||||
// Fix case 'C:/' to 'C:\'
|
||||
if (chars.Length > 2 && !char.IsDigit(chars[0]) && chars[1] == ':')
|
||||
{
|
||||
chars[2] = '\\';
|
||||
}
|
||||
|
||||
return new string(chars);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the relative parts from the path. For instance it replaces 'xx/yy/../zz' with 'xx/zz'.
|
||||
/// </summary>
|
||||
/// <param name="path">The input path.</param>
|
||||
/// <returns>The output path.</returns>
|
||||
public static string RemovePathRelativeParts(string path)
|
||||
{
|
||||
if (string.IsNullOrEmpty(path))
|
||||
return path;
|
||||
|
||||
path = NormalizePath(path);
|
||||
|
||||
string[] components = path.Split('/');
|
||||
|
||||
Stack<string> stack = new Stack<string>();
|
||||
foreach (var bit in components)
|
||||
{
|
||||
if (bit == "..")
|
||||
{
|
||||
if (stack.Count != 0)
|
||||
{
|
||||
var popped = stack.Pop();
|
||||
if (popped == "..")
|
||||
{
|
||||
stack.Push(popped);
|
||||
stack.Push(bit);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
stack.Push(bit);
|
||||
}
|
||||
}
|
||||
else if (bit == ".")
|
||||
{
|
||||
// Skip /./
|
||||
}
|
||||
else
|
||||
{
|
||||
stack.Push(bit);
|
||||
}
|
||||
}
|
||||
|
||||
bool isRooted = path.StartsWith("/");
|
||||
string result = string.Join(Path.DirectorySeparatorChar.ToString(), stack.Reverse());
|
||||
if (isRooted)
|
||||
result = result.Insert(0, "/");
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes the file contents. Before writing reads the existing file and discards operation if contents are the same.
|
||||
/// </summary>
|
||||
/// <param name="path">The path.</param>
|
||||
/// <param name="contents">The file contents.</param>
|
||||
/// <returns>True if file has been modified, otherwise false.</returns>
|
||||
public static bool WriteFileIfChanged(string path, string contents)
|
||||
{
|
||||
if (File.Exists(path))
|
||||
{
|
||||
string oldContents = null;
|
||||
try
|
||||
{
|
||||
oldContents = File.ReadAllText(path);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
Log.Warning(string.Format("Failed to read file contents while trying to save it.", path));
|
||||
}
|
||||
|
||||
if (string.Equals(contents, oldContents, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
Log.Verbose(string.Format("Skipped saving file to {0}", path));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(path));
|
||||
File.WriteAllText(path, contents, new UTF8Encoding());
|
||||
Log.Verbose(string.Format("Saved file to {0}", path));
|
||||
}
|
||||
catch
|
||||
{
|
||||
Log.Error(string.Format("Failed to save file {0}", path));
|
||||
throw;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Replaces the given text with other one in the files.
|
||||
/// </summary>
|
||||
/// <param name="folderPath">The relative or absolute path to the directory to search.</param>
|
||||
/// <param name="searchPattern">The search string to match against the names of files in <paramref name="folderPath" />. This parameter can contain a combination of valid literal path and wildcard (* and ?) characters (see Remarks), but doesn't support regular expressions.</param>
|
||||
/// <param name="searchOption">One of the enumeration values that specifies whether the search operation should include all subdirectories or only the current directory.</param>
|
||||
/// <param name="findWhat">The text to replace.</param>
|
||||
/// <param name="replaceWith">The value to replace to.</param>
|
||||
public static void ReplaceInFiles(string folderPath, string searchPattern, SearchOption searchOption, string findWhat, string replaceWith)
|
||||
{
|
||||
var files = Directory.GetFiles(folderPath, searchPattern, searchOption);
|
||||
ReplaceInFiles(files, findWhat, replaceWith);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Replaces the given text with other one in the files.
|
||||
/// </summary>
|
||||
/// <param name="files">The list of the files to process.</param>
|
||||
/// <param name="findWhat">The text to replace.</param>
|
||||
/// <param name="replaceWith">The value to replace to.</param>
|
||||
public static void ReplaceInFiles(string[] files, string findWhat, string replaceWith)
|
||||
{
|
||||
foreach (var file in files)
|
||||
{
|
||||
ReplaceInFile(file, findWhat, replaceWith);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Replaces the given text with other one in the file.
|
||||
/// </summary>
|
||||
/// <param name="file">The file to process.</param>
|
||||
/// <param name="findWhat">The text to replace.</param>
|
||||
/// <param name="replaceWith">The value to replace to.</param>
|
||||
public static void ReplaceInFile(string file, string findWhat, string replaceWith)
|
||||
{
|
||||
var text = File.ReadAllText(file);
|
||||
text = text.Replace(findWhat, replaceWith);
|
||||
File.WriteAllText(file, text);
|
||||
}
|
||||
}
|
||||
}
|
||||
30
Source/Tools/Flax.Build/Utilities/WinAPI.cs
Normal file
30
Source/Tools/Flax.Build/Utilities/WinAPI.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Flax.Build
|
||||
{
|
||||
static class WinAPI
|
||||
{
|
||||
public static class dbghelp
|
||||
{
|
||||
[DllImport("dbghelp.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
public static extern bool SymInitialize(IntPtr hProcess, string UserSearchPath, [MarshalAs(UnmanagedType.Bool)] bool fInvadeProcess);
|
||||
|
||||
[DllImport("dbghelp.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
public static extern bool SymCleanup(IntPtr hProcess);
|
||||
|
||||
[DllImport("dbghelp.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
||||
public static extern ulong SymLoadModuleEx(IntPtr hProcess, IntPtr hFile, string ImageName, string ModuleName, long BaseOfDll, int DllSize, IntPtr Data, int Flags);
|
||||
|
||||
[DllImport("dbghelp.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
public static extern bool SymEnumerateSymbols64(IntPtr hProcess, ulong BaseOfDll, SymEnumerateSymbolsProc64 EnumSymbolsCallback, IntPtr UserContext);
|
||||
|
||||
public delegate bool SymEnumerateSymbolsProc64(string SymbolName, ulong SymbolAddress, uint SymbolSize, IntPtr UserContext);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user