@ -10,6 +10,7 @@ using System.Collections.Generic;
@@ -10,6 +10,7 @@ using System.Collections.Generic;
using System.Linq ;
using Mono.VisualC.Interop ;
using Mono.VisualC.Interop.ABI ;
using Mono.VisualC.Interop.Util ;
using Mono.VisualC.Code ;
using Mono.VisualC.Code.Atoms ;
@ -25,17 +26,9 @@ namespace CPPInterop {
@@ -25,17 +26,9 @@ namespace CPPInterop {
public static readonly string [ ] genericTypeArgs = new string [ ] { "T" , "U" , "V" , "W" , "X" , "Y" , "Z" } ;
public string Source { get ; set ; }
private CodeUnit currentUnit ;
public Dictionary < string , string > Classes ;
private CodeUnit enumerations ;
public HashSet < string > Enumerations ;
private CodeUnit unions ;
public HashSet < string > Unions ;
public string Dir { get ; set ; }
public bool ShouldValidate { get ; set ; }
public string Library { get ; set ; }
private string nspace ;
public string Namespace {
@ -47,18 +40,28 @@ namespace CPPInterop {
@@ -47,18 +40,28 @@ namespace CPPInterop {
}
}
public string Library { get ; set ; }
public Dictionary < string , string > Classes ;
public HashSet < string > Enumerations ;
public HashSet < string > Unions ;
public Dictionary < string , CodeTypeDeclaration > UnknownTypes ;
public CodeDomProvider Provider { get ; set ; }
public CodeGeneratorOptions Options { get ; set ; }
private CodeUnit currentUnit ;
private CodeUnit enumerations ;
private CodeUnit unions ;
private Dictionary < string , Property > properties ;
private HashSet < string > fileList ;
public static void Main ( string [ ] args )
{
bool help = false ;
Generator gen = new Generator ( ) ;
var p = new OptionSet ( ) {
{ "h|?|help" , v = > help = v ! = null } ,
{ "validate" , v = > gen . ShouldValidate = v ! = null } ,
{ "f=" , v = > gen . Source = v } ,
{ "o=" , v = > gen . Dir = v } ,
{ "ns=" , v = > gen . Namespace = v } ,
@ -109,27 +112,41 @@ namespace CPPInterop {
@@ -109,27 +112,41 @@ namespace CPPInterop {
public Generator ( )
{
Classes = new Dictionary < string , string > ( ) ;
UnknownTypes = new Dictionary < string , CodeTypeDeclaration > ( ) ;
Enumerations = new HashSet < string > ( ) ;
enumerations = new CodeUnit { ManagedNamespace = Namespace } ;
Unions = new HashSet < string > ( ) ;
unions = new CodeUnit { ManagedNamespace = Namespace } ;
enumerations = new CodeUnit { ManagedNamespace = Namespace } ;
unions = new CodeUnit { ManagedNamespace = Namespace } ;
properties = new Dictionary < string , Property > ( ) ;
fileList = new HashSet < string > ( ) ;
}
public void Run ( )
{
Console . WriteLine ( "Generating bindings..." ) ;
XmlDocument xmldoc = new XmlDocument ( ) ;
xmldoc . Load ( Source ) ;
// FIXME: Support namespaces!!!
//XmlNodeList namespaces = xmldoc.SelectNodes ("/GCC_XML/Namespace[@name != '::' and @name != '' and @name != 'std']");
ProcessClasses ( xmldoc ) ;
GenerateStaticLibField ( ) ;
SaveFile ( enumerations . WrapperToCodeDom ( ) , "Enums" ) ;
SaveFile ( unions . WrapperToCodeDom ( ) , "Unions" ) ;
if ( enumerations . Atoms . Any ( ) )
SaveFile ( enumerations . WrapperToCodeDom ( Provider ) , "Enums" ) ;
if ( unions . Atoms . Any ( ) )
SaveFile ( unions . WrapperToCodeDom ( Provider ) , "Unions" ) ;
if ( ShouldValidate ) {
GenerateUnknownTypeStubs ( ) ;
Validate ( ) ;
//File.Delete (fileList.Where (f => f.StartsWith ("UnknownTypes")).Single ());
}
}
void ProcessClasses ( XmlDocument xmldoc )
@ -172,7 +189,14 @@ namespace CPPInterop {
@@ -172,7 +189,14 @@ namespace CPPInterop {
for ( int i = 0 ; i < replaceArgs . Length ; i + + )
classAtom . TemplateArguments . Add ( genericTypeArgs [ i ] ) ;
}
currentUnit . Atoms . AddLast ( classAtom ) ;
Classes . Add ( name , fname ( name ) ) ;
CppType currentType = new CppType ( CppTypes . Class , name ) ; ;
if ( replaceArgs ! = null )
currentType . Modify ( new CppModifiers . TemplateModifier ( replaceArgs ) ) ;
CheckType ( currentType ) ;
foreach ( XmlNode baseNode in clas . SelectNodes ( "Base" ) ) {
classAtom . Bases . Add ( new Class . BaseClass {
@ -185,7 +209,8 @@ namespace CPPInterop {
@@ -185,7 +209,8 @@ namespace CPPInterop {
}
//string size = clas.Attributes["size"].Value;
var members = clas . Attributes [ "members" ] . Value . Split ( ' ' ) . Where ( id = > ! id . Equals ( string . Empty ) ) ;
var members = clas . Attributes [ "members" ] . Value . Split ( ' ' ) . Where ( id = > ! id . Equals ( string . Empty ) ) . ToArray ( ) ;
Dictionary < MethodSignature , string > methods = new Dictionary < MethodSignature , string > ( ) ;
foreach ( string id in members ) {
XmlNode n = xmldoc . SelectSingleNode ( "/GCC_XML/*[@id='" + id + "']" ) ;
@ -205,23 +230,25 @@ namespace CPPInterop {
@@ -205,23 +230,25 @@ namespace CPPInterop {
break ;
case "Field" :
CppType fieldType = findType ( xmldoc . DocumentElement , n . Attributes [ "type" ] ) ;
string fn ame = "field" + fieldCount + + ;
string fieldN ame = "field" + fieldCount + + ;
if ( n . Attributes [ "name" ] ! = null & & n . Attributes [ "name" ] . Value ! = "" )
fn ame = n . Attributes [ "name" ] . Value ;
classAtom . Atoms . AddLast ( new Field ( fn ame , fieldType ) ) ;
fieldN ame = n . Attributes [ "name" ] . Value ;
classAtom . Atoms . AddLast ( new Field ( fieldN ame , fieldType ) ) ;
continue ;
default :
continue ;
}
if ( n . Attributes [ "access" ] = = null | | n . Attributes [ "access" ] . Value ! = "public" | |
( n . Attributes [ "overrides" ] ! = null & & n . Attributes [ "overrides" ] . Value ! = "" & & ! dtor ) | |
n . Attributes [ "extern" ] = = null | | n . Attributes [ "extern" ] . Value ! = "1" )
// Now we're processing a method...
if ( n . Attributes [ "access" ] = = null | | n . Attributes [ "access" ] . Value ! = "public" | |
( n . Attributes [ "overrides" ] ! = null & & n . Attributes [ "overrides" ] . Value ! = "" & & ! dtor ) | |
n . Attributes [ "extern" ] = = null | | n . Attributes [ "extern" ] . Value ! = "1" )
continue ;
string mname = n . Attributes [ "name" ] . Value ;
string mname = n . Attributes [ "name" ] . Value ;
CppType retType = findType ( xmldoc . DocumentElement , n . Attributes [ "returns" ] ) ;
CppType retType = findType ( xmldoc . DocumentElement , n . Attributes [ "returns" ] ) ;
if ( replaceArgs ! = null ) {
for ( int i = 0 ; i < replaceArgs . Length ; i + + )
retType = replaceType ( retType , replaceArgs [ i ] , genericTypeArgs [ i ] ) ;
@ -229,35 +256,63 @@ namespace CPPInterop {
@@ -229,35 +256,63 @@ namespace CPPInterop {
var methodAtom = new Method ( dtor ? "Destruct" : mname ) {
RetType = retType ,
IsVirtual = n . Attributes [ "virtual" ] ! = null & & n . Attributes [ "virtual" ] . Value = = "1" ,
IsStatic = n . Attributes [ "static" ] ! = null & & n . Attributes [ "static" ] . Value = = "1" ,
IsConst = n . Attributes [ "const" ] ! = null & & n . Attributes [ "const" ] . Value = = "1" ,
IsVirtual = n . Attributes [ "virtual" ] ! = null & & n . Attributes [ "virtual" ] . Value = = "1" ,
IsStatic = n . Attributes [ "static" ] ! = null & & n . Attributes [ "static" ] . Value = = "1" ,
IsConst = n . Attributes [ "const" ] ! = null & & n . Attributes [ "const" ] . Value = = "1" ,
IsConstructor = ctor ,
IsDestructor = dtor
} ;
if ( ShouldValidate )
methodAtom . Mangled = new NameTypePair < Type > { Name = n . Attributes [ "mangled" ] . Value , Type = typeof ( ItaniumAbi ) } ;
XmlNodeList argNodes = n . SelectNodes ( "Argument" ) ;
CppType [ ] argTypes = new CppType [ argNodes . Count ] ;
int c = 0 ;
foreach ( XmlNode arg in n . SelectNodes ( "Argument" ) ) {
foreach ( XmlNode arg in argNodes ) {
string argname ;
if ( arg . Attributes [ "name" ] = = null )
if ( arg . Attributes [ "name" ] = = null )
argname = "arg" + c ;
else
argname = arg . Attributes [ "name" ] . Value ;
argname = arg . Attributes [ "name" ] . Value ;
CppType argtype = findType ( xmldoc . DocumentElement , arg . Attributes [ "type" ] . Value ) ;
CppType argtype = findType ( xmldoc . DocumentElement , arg . Attributes [ "type" ] . Value ) ;
if ( replaceArgs ! = null ) {
for ( int i = 0 ; i < replaceArgs . Length ; i + + )
argtype = replaceType ( argtype , replaceArgs [ i ] , genericTypeArgs [ i ] ) ;
}
methodAtom . Parameters . Add ( new Method . Parameter { Name = argname , Type = argtype } ) ;
methodAtom . Parameters . Add ( new NameTypePair < CppType > { Name = argname , Type = argtype } ) ;
argTypes [ c ] = argtype ;
// tee hee
c + + ;
}
// if it's const, returns a value, and has no parameters, assume it's a property getter (for now?)
if ( methodAtom . IsConst & & ! retType . Equals ( CppTypes . Void ) & & ! methodAtom . Parameters . Any ( ) ) {
// Try to filter out duplicate methods
MethodSignature sig = new MethodSignature { Name = methodAtom . FormattedName , Arguments = argTypes } ;
string conflictingSig ;
if ( methods . TryGetValue ( sig , out conflictingSig ) ) {
// FIXME: add comment to explain why it's commented out
string demangled = n . Attributes [ "demangled" ] . Value ;
Console . Error . WriteLine ( "Warning: Method \"{0}\" in class {1} omitted because it conflicts with \"{2}\"" , demangled , name , conflictingSig ) ;
methodAtom . Comment = string . Format ( "FIXME:Method \"{0}\" omitted because it conflicts with \"{1}\"" , demangled , conflictingSig ) ;
methodAtom . CommentedOut = true ;
} else
methods . Add ( sig , n . Attributes [ "demangled" ] . Value ) ;
// Record unknown types in parameters and return type
CheckType ( methodAtom . RetType ) ;
foreach ( var arg in methodAtom . Parameters )
CheckType ( arg . Type ) ;
// Detect if this is a property...
// if it's const, returns a value, has no parameters, and there is no other method with the same name
// in this class assume it's a property getter (for now?)
if ( methodAtom . IsConst & & ! retType . Equals ( CppTypes . Void ) & & ! methodAtom . Parameters . Any ( ) & &
findMethod ( xmldoc . DocumentElement , members , "@id != '" + id + "' and @name = '" + mname + "'" ) = = null ) {
var pname = methodAtom . FormattedName ;
Property propertyAtom ;
@ -273,17 +328,17 @@ namespace CPPInterop {
@@ -273,17 +328,17 @@ namespace CPPInterop {
}
// if it's name starts with "set", does not return a value, and has one arg (besides this ptr)
// AND there is a corresponding getter method, then assume it's a property setter
if ( mname . ToLower ( ) . StartsWith ( "set" ) & & retType . Equals ( CppTypes . Void ) & & methodAtom . Parameters . Count = = 1 ) {
// and there is no other method with the same name...
if ( mname . ToLower ( ) . StartsWith ( "set" ) & & retType . Equals ( CppTypes . Void ) & & methodAtom . Parameters . Count = = 1 & &
findMethod ( xmldoc . DocumentElement , members , "@id != '" + id + "' and @name = '" + mname + "'" ) = = null ) {
string getterName = "translate(@name, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz') = '" +
mname . Substring ( 3 ) . TrimStart ( '_' ) . ToLower ( ) + "'" ;
string isMember = "@id = '" + string . Join ( "' or @id = '" , members . ToArray ( ) ) + "'" ;
string pname = methodAtom . FormattedName . Substring ( 3 ) ;
Property propertyAtom = null ;
// FIXME: This xpath is probably very slow if the class has many members
if ( properties . TryGetValue ( pname , out propertyAtom ) | |
xmldoc . SelectSingleNode ( string . Format ( "/GCC_XML/Method[{0} and ({1})]" , getterName , isMember ) ) ! = null ) {
// ...AND there is a corresponding getter method, then assume it's a property setter
if ( properties . TryGetValue ( pname , out propertyAtom ) | | findMethod ( xmldoc . DocumentElement , members , getterName ) ! = null ) {
if ( propertyAtom ! = null ) {
propertyAtom . Setter = methodAtom ;
@ -294,7 +349,7 @@ namespace CPPInterop {
@@ -294,7 +349,7 @@ namespace CPPInterop {
}
// set the method's arg name to "value" so that the prop setter works right
Method . Paramete r valueParam = methodAtom . Parameters [ 0 ] ;
va r valueParam = methodAtom . Parameters [ 0 ] ;
valueParam . Name = "value" ;
methodAtom . Parameters [ 0 ] = valueParam ;
@ -305,66 +360,33 @@ namespace CPPInterop {
@@ -305,66 +360,33 @@ namespace CPPInterop {
classAtom . Atoms . AddLast ( methodAtom ) ;
}
Classes . Add ( name , sanitize ( name ) + "." + Provider . FileExtension ) ;
SaveFile ( currentUnit . WrapperToCodeDom ( ) , name ) ;
}
}
XmlNode find ( XmlNode root , XmlAttribute att )
{
if ( att ! = null )
return find ( root , att . Value ) ;
return null ;
}
XmlNode find ( XmlNode root , string id )
{
XmlNode n = root . SelectSingleNode ( "/GCC_XML/*[@id='" + id + "']" ) ;
//if (n.Name == "Typedef")
// return n;
if ( n . Attributes [ "type" ] ! = null )
return find ( root , n . Attributes [ "type" ] . Value ) ;
return n ;
}
CppType findType ( XmlNode root , XmlAttribute att )
{
if ( att ! = null )
return findType ( root , att . Value ) ;
return CppTypes . Void ;
}
CppType findType ( XmlNode root , string id )
{
return findType ( root , id , new CppType ( ) ) ;
SaveFile ( currentUnit . WrapperToCodeDom ( Provider ) , name ) ;
}
}
CppType findType ( XmlNode root , string id , CppType modifiers )
void Validate ( )
{
XmlNode n = root . SelectSingleNode ( "/GCC_XML/*[@id='" + id + "'] ") ;
Console . WriteLine ( "Validating bindings..." ) ;
string name = "unknown" ;
if ( n . Attributes [ "name" ] ! = null )
name = n . Attributes [ "name" ] . Value ;
switch ( n . Name ) {
case "ArrayType" : return findType ( root , n . Attributes [ "type" ] . Value , modifiers . Modify ( CppModifiers . Array ) ) ;
case "PointerType" : return findType ( root , n . Attributes [ "type" ] . Value , modifiers . Modify ( CppModifiers . Pointer ) ) ;
case "ReferenceType" : return findType ( root , n . Attributes [ "type" ] . Value , modifiers . Modify ( CppModifiers . Reference ) ) ;
case "CvQualifiedType" :
return findType ( root , n . Attributes [ "type" ] . Value ,
modifiers . Modify ( n . Attributes [ "const" ] ! = null & & n . Attributes [ "const" ] . Value = = "1" ? CppModifiers . Const : CppModifiers . Volatile ) ) ;
var compileParams = new CompilerParameters {
GenerateInMemory = true ,
TreatWarningsAsErrors = false
} ;
compileParams . ReferencedAssemblies . Add ( typeof ( CppLibrary ) . Assembly . CodeBase ) ;
case "Typedef" : return findType ( root , n . Attributes [ "type" ] . Value , modifiers ) ;
CompilerResults results = Provider . CompileAssemblyFromFile ( compileParams , fileList . ToArray ( ) ) ;
case "FundamentalType" : return modifiers . ApplyTo ( new CppType ( name ) ) ;
case "Class" : return modifiers . ApplyTo ( new CppType ( CppTypes . Class , name ) ) ;
case "Struct" : return modifiers . ApplyTo ( new CppType ( CppTypes . Struct , name ) ) ;
case "Union" : return modifiers . ApplyTo ( ProcessUnion ( root , n ) ) ;
case "Enumeration" : return modifiers . ApplyTo ( ProcessEnum ( n ) ) ;
if ( results . Errors . Count > 0 ) {
foreach ( CompilerError error in results . Errors )
Console . Error . WriteLine ( "{0}({1},{2}): error {3}: {4}" , error . FileName , error . Line , error . Column , error . ErrorNumber , error . ErrorText ) ;
// FIXME: support function pointers betters
case "FunctionType" : return modifiers . ApplyTo ( CppTypes . Void ) ;
Console . Error . WriteLine ( "Validation failed with {0} compilation errors." , results . Errors . Count ) ;
}
throw new NotImplementedException ( "Unknown type node: " + n . Name ) ;
//Type [] types = validationAssembly.GetExportedTypes ();
// do stuff...
}
CppType ProcessEnum ( XmlNode enm )
@ -431,6 +453,10 @@ namespace CPPInterop {
@@ -431,6 +453,10 @@ namespace CPPInterop {
void GenerateStaticLibField ( )
{
string name = "Lib_" + Library ;
if ( File . Exists ( Path . Combine ( Dir , fname ( name ) ) ) )
return ;
var ccu = new CodeCompileUnit ( ) ;
var ns = new CodeNamespace ( Namespace ) ;
var cls = new CodeTypeDeclaration ( "Libs" ) {
@ -446,16 +472,41 @@ namespace CPPInterop {
@@ -446,16 +472,41 @@ namespace CPPInterop {
ns . Types . Add ( cls ) ;
ccu . Namespaces . Add ( ns ) ;
SaveFile ( ccu , "Lib_" + Library ) ;
SaveFile ( ccu , name ) ;
}
void GenerateUnknownTypeStubs ( )
{
var ccu = new CodeCompileUnit ( ) ;
var ns = new CodeNamespace ( Namespace ) ;
foreach ( var type in UnknownTypes )
ns . Types . Add ( type . Value ) ;
ccu . Namespaces . Add ( ns ) ;
SaveFile ( ccu , "UnknownTypes" ) ;
}
void SaveFile ( CodeCompileUnit ccu , string baseName )
{
var sw = File . CreateText ( Path . Combine ( Dir , sanitize ( baseName ) + "." + Provider . FileExtension ) ) ;
string name = Path . Combine ( Dir , fname ( baseName ) ) ;
if ( File . Exists ( name ) & & fileList . Contains ( name ) )
return ;
if ( File . Exists ( name ) & & ! fileList . Contains ( name ) ) {
int i = 1 ;
while ( File . Exists ( Path . Combine ( Dir , fname ( baseName + i ) ) ) )
i + + ;
name = fname ( baseName + i ) ;
}
var sw = File . CreateText ( Path . Combine ( Dir , name ) ) ;
Provider . GenerateCodeFromCompileUnit ( ccu , sw , Options ) ;
sw . Flush ( ) ;
sw . Close ( ) ;
sw . Dispose ( ) ;
if ( ! fileList . Contains ( name ) )
fileList . Add ( name ) ;
}
static CppType replaceType ( CppType inType , CppType toReplace , string tn )
@ -473,9 +524,100 @@ namespace CPPInterop {
@@ -473,9 +524,100 @@ namespace CPPInterop {
return inType ;
}
static string sanitize ( string name )
// FIXME: Do something trickier than just throw ElementTypeName in here?
void CheckType ( CppType cpptype )
{
string type = cpptype . ElementTypeName ;
if ( type = = null ) return ;
bool typeFound = Classes . ContainsKey ( type ) | | Enumerations . Contains ( type ) | | Unions . Contains ( type ) ;
bool alreadyUnknown = UnknownTypes . ContainsKey ( type ) ;
if ( ! typeFound & & ! alreadyUnknown ) {
var ctd = new CodeTypeDeclaration ( type ) {
TypeAttributes = TypeAttributes . Public ,
IsClass = true ,
IsPartial = true
} ;
if ( cpptype . Modifiers . Contains ( CppModifiers . Template ) ) {
var template = cpptype . Modifiers . OfType < CppModifiers . TemplateModifier > ( ) . Single ( ) ;
for ( int i = 0 ; i < template . Types . Length ; i + + )
ctd . TypeParameters . Add ( genericTypeArgs [ i ] ) ;
}
UnknownTypes . Add ( type , ctd ) ;
} else if ( typeFound & & alreadyUnknown )
UnknownTypes . Remove ( type ) ;
}
static XmlNode find ( XmlNode root , XmlAttribute att )
{
if ( att ! = null )
return find ( root , att . Value ) ;
return null ;
}
static XmlNode find ( XmlNode root , string id )
{
XmlNode n = root . SelectSingleNode ( "/GCC_XML/*[@id='" + id + "']" ) ;
//if (n.Name == "Typedef")
// return n;
if ( n . Attributes [ "type" ] ! = null )
return find ( root , n . Attributes [ "type" ] . Value ) ;
return n ;
}
static XmlNode findMethod ( XmlNode root , string [ ] members , string predicate )
{
string isMember = "@id = '" + string . Join ( "' or @id = '" , members ) + "'" ;
return root . SelectSingleNode ( string . Format ( "/GCC_XML/Method[({0}) and ({1})]" , predicate , isMember ) ) ;
}
CppType findType ( XmlNode root , XmlAttribute att )
{
if ( att ! = null )
return findType ( root , att . Value ) ;
return CppTypes . Void ;
}
CppType findType ( XmlNode root , string id )
{
return findType ( root , id , new CppType ( ) ) ;
}
CppType findType ( XmlNode root , string id , CppType modifiers )
{
XmlNode n = root . SelectSingleNode ( "/GCC_XML/*[@id='" + id + "']" ) ;
string name = "unknown" ;
if ( n . Attributes [ "name" ] ! = null )
name = n . Attributes [ "name" ] . Value ;
switch ( n . Name ) {
case "ArrayType" : return findType ( root , n . Attributes [ "type" ] . Value , modifiers . Modify ( CppModifiers . Array ) ) ;
case "PointerType" : return findType ( root , n . Attributes [ "type" ] . Value , modifiers . Modify ( CppModifiers . Pointer ) ) ;
case "ReferenceType" : return findType ( root , n . Attributes [ "type" ] . Value , modifiers . Modify ( CppModifiers . Reference ) ) ;
case "CvQualifiedType" :
return findType ( root , n . Attributes [ "type" ] . Value ,
modifiers . Modify ( n . Attributes [ "const" ] ! = null & & n . Attributes [ "const" ] . Value = = "1" ? CppModifiers . Const : CppModifiers . Volatile ) ) ;
case "Typedef" : return findType ( root , n . Attributes [ "type" ] . Value , modifiers ) ;
case "FundamentalType" : return modifiers . ApplyTo ( new CppType ( name ) ) ;
case "Class" : return modifiers . ApplyTo ( new CppType ( CppTypes . Class , name ) ) ;
case "Struct" : return modifiers . ApplyTo ( new CppType ( CppTypes . Struct , name ) ) ;
case "Union" : return modifiers . ApplyTo ( ProcessUnion ( root , n ) ) ;
case "Enumeration" : return modifiers . ApplyTo ( ProcessEnum ( n ) ) ;
// FIXME: support function pointers betters
case "FunctionType" : return modifiers . ApplyTo ( CppTypes . Void ) ;
}
throw new NotImplementedException ( "Unknown type node: " + n . Name ) ;
}
string fname ( string name )
{
return name . Replace ( "<" , "_" ) . Replace ( ">" , "_" ) . Replace ( ":" , "_" ) . Replace ( "*" , "_" ) . Replace ( "," , "_" ) . Replace ( " " , "_" ) ;
return name . Replace ( "<" , "_" ) . Replace ( ">" , "_" ) . Replace ( ":" , "_" ) . Replace ( "*" , "_" ) . Replace ( "," , "_" ) . Replace ( " " , "_" ) + "." + Provider . FileExtension ;
}
}
}