By using this site, you agree to our updated Privacy Policy and our Terms of Use. Manage your Cookies Settings.
435,054 Members | 1,304 Online
Bytes IT Community
+ Ask a Question
Need help? Post your question and get tips & solutions from a community of 435,054 IT Pros & Developers. It's quick & easy.

dirty submission of INI file access...

P: n/a
While I know this is not the correct venue... I realize this is of
little to no importance to most out there... however, if I had found
this in my initial searches, I would have used this. So, as an
alternative to the mentalis.org's IniReader, I submit the following
files to the web news group DBs...:

[ Do I need to say there are no guarantees... etc??? Please! If you
want to use this, test it thoroughly before going into production! To
do otherwise would be foolish and asking for issues. ]

@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@

{{{{{{{ Start File C_API_INIfileAccess.h }}}}}}}}

#pragma once
#include "ExceptionAssert.h"

using namespace System;

namespace Utility { namespace INI {

/************************************************** *************************/
/************************************************** *************************/
/** This class allows you to simply access INI files as easily as you
once
did before .NET was invented by interating with the API.

Note that this C++/CLI .NET code was written and tested with Studio
2005.

Note that the writes/reads from the ini file limit the data to be
500
(internally noted as 512) bytes of data. Amounts above that will
throw an exception at release or an Assert for a debug build.

Note that all calls assume Unicode characters. Backward
compatibility to 8-bit was not one of my goals.

Note that the project that uses this class should specify in the
linker
options to fill in the section "Additional Library Directories"
with something like "C:\Program Files\Microsoft Visual Studio
8\VC\lib"
although this may differ for your particular install of Visual
Studio
and your location of this folder that contains KERNEL32.LIB.

Note - With some deference to mentalis.org's IniReader, I decided
to write
my own that was not designed for speed but rather designed for
safety and
using a buffer size that was a little more sensible in size
(IMHO).
Please feel free to use the mentalis.org's IniReader as it is
refered to
many time in the news groups... I just prefer the simplicity of
mine
that more directly mimics the original APIs.
*/
ref class C_API_INIfileAccess
{
public:

/************************************************** *************************/
/** This allows you to Write a string into an ini file. */
static bool WritePrivateProfileString( String^ in_Section
,String^ in_Key
,String^ in_Value
,String^ in_Filepath
)
{
bool bSuccess = false;

try {
_m_The_INI_API_AccessLock.AcquireWriterLock( 5000 );
bSuccess = _WritePrivateProfileString( in_Section, in_Key,
in_Value, in_Filepath );
}
catch( ApplicationException^ e ) {
CExceptionAssert::Assert( bSuccess, "Failed call to
GetPrivateProfileString", e);
}
finally {
_m_The_INI_API_AccessLock.ReleaseWriterLock();
}
return bSuccess;
}

/************************************************** *************************/
/** This allows you to Read a string into an ini file. Returns true
only if
a value was actually read from the file. */
static bool GetPrivateProfileString( String^ in_Section
,String^ in_Key
,String^ in_DefaultValue
,String^%
out_returnedStringBuffer
,String^ in_INIFilepath
)
{
bool bSuccess = false;
array<wchar_t>^ bBuffer = gcnew
array<wchar_t>(m_iInternalBufferSize);

try {
unsigned int iNumCharsPlacedInBuffer;
_m_The_INI_API_AccessLock.AcquireWriterLock( 5000 );
iNumCharsPlacedInBuffer = _GetPrivateProfileString( in_Section
, in_Key
,
m_strInternalDefaultString
, bBuffer
,
(m_iInternalBufferSize - 3)
,
in_INIFilepath );
bSuccess = (iNumCharsPlacedInBuffer < (m_iInternalBufferSize -
5));
}
catch( Exception^ e) {
CExceptionAssert::Assert( bSuccess, "Failed call to
GetPrivateProfileString", e);
}
finally {
_m_The_INI_API_AccessLock.ReleaseWriterLock();
}

if(bSuccess) {
out_returnedStringBuffer = gcnew String(bBuffer);
if( out_returnedStringBuffer == m_strInternalDefaultString ) {
bSuccess = false;
out_returnedStringBuffer = in_DefaultValue;
}
}
else {
out_returnedStringBuffer = in_DefaultValue;
CExceptionAssert::Assert(bSuccess,
"C_API_INIfileAccess::GetPrivateProfileString - The file's key value
was larger than " + m_iInternalBufferSize.ToString());
}
return bSuccess;
}

/************************************************** *************************/
/** This allows you to Write an int into an ini file. */
static bool WritePrivateProfileInt( String^ in_Section
,String^ in_Key
,int in_Value
,String^ in_Filepath)
{
String^ strTheInt = Convert::ToString( in_Value );
return WritePrivateProfileString( in_Section, in_Key, strTheInt,
in_Filepath );
}
/************************************************** *************************/
/** This allows you to Read an int into an ini file. */
static bool GetPrivateProfileInt( String^ in_Section
,String^ in_Key
,int% out_Value
,String^ in_Filepath)
{
String^ strTheInt = nullptr;
bool bSuccess = GetPrivateProfileString( in_Section, in_Key,
m_strInternalDefaultString, strTheInt, in_Filepath );

if(!bSuccess || strTheInt == m_strInternalDefaultString) {
out_Value = 0;
return false; //return before we try to convert what we know may
be impossible to convert
}

try {
out_Value = Convert::ToInt32( strTheInt );
}
catch( ApplicationException^ e ) {
bSuccess = false;
if(strTheInt == nullptr)
strTheInt = "";
CExceptionAssert::Assert( bSuccess, "Failed call to convert
string ["+strTheInt+"] into int", e);
}
return bSuccess;
}

/************************************************** *************************/
/** This allows you to Write a double into an ini file. */
static bool WritePrivateProfileDouble( String^ in_Section
,String^ in_Key
,double in_Value
,String^ in_Filepath)
{
String^ strTheDouble = Convert::ToString( in_Value );
return WritePrivateProfileString( in_Section, in_Key, strTheDouble,
in_Filepath );
}
/************************************************** *************************/
/** This allows you to Read a double into an ini file. */
static bool GetPrivateProfileDouble( String^ in_Section
,String^ in_Key
,double% out_Value
,String^ in_Filepath)
{
String^ strTheDouble = nullptr;
bool bSuccess = GetPrivateProfileString( in_Section, in_Key,
m_strInternalDefaultString, strTheDouble, in_Filepath );

if(!bSuccess || strTheDouble == m_strInternalDefaultString) {
out_Value = 0;
return false; //return before we try to convert what we know may
be impossible to convert
}

try {
out_Value = Convert::ToDouble( strTheDouble );
}
catch( ApplicationException^ e ) {
bSuccess = false;
if(strTheDouble == nullptr)
strTheDouble = "";
CExceptionAssert::Assert( bSuccess, "Failed call to convert
string ["+strTheDouble+"] into double", e);
}
return bSuccess;
}
private:
/* Note that the project that uses this class should specify in the
linker
options to fill in the section "Additional Library Directories"
with something like "C:\Program Files\Microsoft Visual Studio
8\VC\lib"
although this may differ for your particular install of Visual
Studio
and your location of this folder that contains KERNEL32.LIB.
*/

/************************************************** *************************/
/** Actual call into API - User code should NOT use this method
directly! */

[DllImport("KERNEL32.DLL",EntryPoint="WritePrivateP rofileStringW",SetLastError=true,
CharSet=CharSet::Unicode,ExactSpelling=true,
CallingConvention=CallingConvention::StdCall)]
static bool _WritePrivateProfileString( const String^ Section, const
String^ Key, const String^ Value, const String^ Filepath );
private:

/************************************************** *************************/
/** Actual call into API - User code should NOT use this method
directly! */

[DllImport("KERNEL32.DLL",EntryPoint="GetPrivatePro fileStringW",SetLastError=true,
CharSet=CharSet::Unicode,ExactSpelling=true,
CallingConvention=CallingConvention::StdCall)]
static unsigned int _GetPrivateProfileString( const String^ Section
,const String^ Key
,const String^ DefaultValue
,array<wchar_t>^
returnedStringBuffer
,unsigned int
iSizeofSuppliedBuffer
,const String^ Filepath
);
static const int m_iInternalBufferSize = 512;
/** I make the odd assumption this string below will never be a
string you actualy
would ever use. The idea is that your supplied default string
values would
likely end up in situations where the default value is a likely
candidate for
an actual value. using this below string allows for the
detection of the fefault
more reliably. */
static String^ m_strInternalDefaultString =
"~~DeRfGhNb!*~::+++)))|)>~>>//>/'<<QP5C,";
static ReaderWriterLock _m_The_INI_API_AccessLock;
};

}} //namespace Utility { namespace INI {

{{{{{{{ End File C_API_INIfileAccess.h }}}}}}}}

@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@

{{{{{{{ Start File ExceptionAssert.h }}}}}}}}

#pragma once

using namespace System;
using namespace System::Diagnostics;
namespace Utility { namespace Share {
//################################################## ##########################
//################################################## ##########################
// CExceptionAssert
//################################################## ##########################
//################################################## ##########################

/** Given that a release compile does NOT define "_DEBUG", this allows
you to
easily use the Debug.Assert and automatically convert to thrown
exception
at release time. It will also Capture this output to Log file in
the
Common Application Data folder.
<br/>
<hr/>
The 4 types of Assert( ... ) methods will only take action
if the condition is false.
<br/> The 2 types of LogMessage( ... ) methods will always log to
same CExceptionAssert file log.
<br/> The 2 types of LogMessageIfFalse( ... ) methods will log to
same CExceptionAssert file log
if condition is false but will not assert or throw an
exception.
<hr/>
*/
public ref class CExceptionAssert
{
public:
/** This allows you to easily use the Debug.Assert and
automatically convert to thrown exception at release time. */
static inline void Assert( bool condition ) {
if(!condition) {
theLock.AcquireWriterLock( Threading::Timeout::Infinite );
try {
StackTrace^ st = gcnew StackTrace( true );
DateTime dt = DateTime::UtcNow;

Debug::WriteLine( m_strCurrentTimeLogString +"At Stack
Trace:\r\n" + st->ToString(), m_strCategory);
#ifdef _DEBUG
Debug::Assert( condition );
#endif
throw gcnew ApplicationException("At Stack Trace:\r\n" +
st->ToString());
}
finally {
theLock.ReleaseWriterLock();
}
}
}

/** This allows you to easily use the Debug.Assert and
automatically convert to thrown exception at release time. */
static inline void Assert( bool condition, System::String^ message
) {
//only perform actions if the condition was false
if(!condition) {
theLock.AcquireWriterLock( Threading::Timeout::Infinite );
try {
StackTrace^ st = gcnew StackTrace( true );
DateTime dt = DateTime::UtcNow;

Debug::WriteLine( m_strCurrentTimeLogString + message +
"\r\n:\r\n" + "At Stack Trace:\r\n" + st->ToString(), m_strCategory);
#ifdef _DEBUG
Debug::Assert( condition, message );
#endif
throw gcnew ApplicationException( message + "\r\n:\r\n" + "At
Stack Trace:\r\n" + st->ToString() );
}
finally {
theLock.ReleaseWriterLock();
}
}
}

/** This allows you to easily use the Debug.Assert and
automatically convert to thrown exception at release time. */
static void Assert( bool condition, Exception^ innerException ) {
if(!condition) {
theLock.AcquireWriterLock( Threading::Timeout::Infinite );
try {
StackTrace^ st = gcnew StackTrace( true );
DateTime dt = DateTime::UtcNow;

Debug::WriteLine( m_strCurrentTimeLogString + "Inner
Exception Message: " + innerException->Message + "\r\n:\r\n" + "At
Stack Trace:\r\n" + st->ToString(), m_strCategory);
#ifdef _DEBUG
Debug::Assert( condition, "Inner Exception Message: " +
innerException->Message );
#endif
throw gcnew ApplicationException("Inner Exception Message: "
+ innerException->Message + "\r\n:\r\n" + "At Stack Trace:\r\n" +
st->ToString(), innerException);
}
finally {
theLock.ReleaseWriterLock();
}
}
}

/** This allows you to easily use the Debug.Assert and
automatically convert to thrown exception at release time. */
static void Assert( bool condition, System::String^ message,
Exception^ innerException ) {
if(!condition) {
theLock.AcquireWriterLock( Threading::Timeout::Infinite );
try {
StackTrace^ st = gcnew StackTrace( true );
DateTime dt = DateTime::UtcNow;

Debug::WriteLine( m_strCurrentTimeLogString + message +
"\r\n:\r\n" + "Inner Exception Message: " + innerException->Message +
"\r\n:\r\n" + "At Stack Trace:\r\n" + st->ToString(), m_strCategory);
#ifdef _DEBUG
Debug::Assert( condition, message + "\r\n:\r\n" + "Inner
Exception Message: " + innerException->Message);
#endif
throw gcnew ApplicationException( message + "\r\n:\r\n" +
"Inner Exception Message: " + innerException->Message + "\r\n:\r\n" +
st->ToString(), innerException);
}
finally {
theLock.ReleaseWriterLock();
}
}
}

/** This will allow you to throw a message to the same Log File as
the Assert() methods
of this class without having to call Assert with a failed
condition.
This handles the following calls:
<pre>
@@ CExceptionAssert.LogMessage( "{0} Hello {1} dude {2}!",
"Happy", "Jimmy", 3 );
@@ CExceptionAssert.LogMessage( "Hello dude!" );
@@ CExceptionAssert.LogMessage( "Uhh Ohhhh dude! {0}" );
@@ CExceptionAssert.LogMessage( "{1} Hello {2} dude {0}!",
"Happy", "Jimmy", 3 );
</pre>
...and it willl gracefully handle mis-managed formated strings as
well.
*/
static void LogMessage( String^ strFormat, ... array<Object^>^ args
) {
Assert( strFormat != nullptr, "The string given to LogMessage was
NULL!" );
String^ theMessage = nullptr;
theLock.AcquireWriterLock( Threading::Timeout::Infinite );
try {
if( (args != nullptr) && (args->Length > 0) ) {
try {
theMessage = String::Format( strFormat, args );
}
catch(FormatException^ e) {
theMessage = "Insufficient args for the formatted string ["
+ strFormat + "] : " + e->Message;
}
}
else {
theMessage = strFormat;
}
DateTime dt = DateTime::UtcNow;
Debug::WriteLine( m_strCurrentTimeLogString + "Message: " +
theMessage );
Debug::Flush();
}
finally {
theLock.ReleaseWriterLock();
}
}

/** Same as LogMessage but only logs message if condition is false.
*/
static void LogMessageIfFalse( bool bCondition, String^ strFormat,
.... array<Object^>^ args ) {
if(!bCondition)
LogMessage(strFormat, args);
}

/** This does the same thing as LogMessage() except it adds a stack
trace to the log file entry. */
static void LogMessageWithStackTrace( String^ strFormat, ...
array<Object^>^ args ) {
Assert( strFormat != nullptr, "The string given to LogMessage was
NULL!" );
String^ theMessage = nullptr;
theLock.AcquireWriterLock( Threading::Timeout::Infinite );
try {
StackTrace^ st = gcnew StackTrace( true );

if( (args != nullptr) && (args->Length > 0) ) {
try {
theMessage = String::Format( strFormat, args );
}
catch(FormatException^ e) {
theMessage = "Insufficient args for the formatted string ["
+ strFormat + "] : " + e->Message;
}
}
else {
theMessage = strFormat;
}
DateTime dt = DateTime::UtcNow;
Debug::WriteLine( m_strCurrentTimeLogString + "Message: " +
theMessage + "\r\n:\r\n" + "At Stack Trace:\r\n" + st->ToString() );
Debug::Flush();
}
finally {
theLock.ReleaseWriterLock();
}
}
/** Same as LogMessageWithStackTrace but only logs message if
condition is false. */
static void LogMessageIfFalseWithStackTrace( bool bCondition,
String^ strFormat, ... array<Object^>^ args ) {
if(!bCondition)
LogMessageWithStackTrace(strFormat, args);
}

private:

/** Static Constructor - Init the capture of the Debug output to a
log file. */
static CExceptionAssert( void ) {
//make sure we start with a clean lock
theLock.ReleaseLock();

//Create Log in CurrentUser App Data
Reflection::AssemblyName TheAssemblyName(
(Reflection::Assembly::GetCallingAssembly())->FullName );
array<wchar_t>^ aBadFilenameChars =
IO::Path::GetInvalidFileNameChars();

String^ strDate = (DateTime::Now).ToShortDateString();
String^ strTime = (DateTime::Now).ToLongTimeString();
String^ strAssemblyName = TheAssemblyName.Name;
String^ strAssemblyVersion = "v" + TheAssemblyName.Version;

for each( wchar_t cc in aBadFilenameChars ) {
strTime = strTime->Replace( cc, '_' );
strDate = strDate->Replace( cc, '_' );
strAssemblyName = strAssemblyName->Replace( cc, '_' );
strAssemblyVersion = strAssemblyVersion->Replace( cc, '_' );
}

//Create the general path for the log files
String^ strPath =
/*
It would be good to change "gerneral path
start" choice below to be
done in the app.config or app.ini file...
*/

Environment::GetFolderPath(
Environment::SpecialFolder::CommonApplicationData )
// Environment::GetFolderPath(
Environment::SpecialFolder::ApplicationData )
// Environment::GetFolderPath(
Environment::SpecialFolder::LocalApplicationData )
// Environment::GetFolderPath(
Environment::SpecialFolder::Personal )

+ "\\"
+ "Your Company Name"

+ "\\"
+ strAssemblyName
+ "\\"
+ strAssemblyVersion
+ "\\"
+ strDate + " - " + strTime
+ "\\"
;

//Create path for the logging files
IO::Directory::CreateDirectory( strPath );

//Create the file that captures the Debug Statements
//This was replaced because the replacement allows another app
to view the log file as it is created.
//IO::Stream^ theFileStreamLog = IO::File::Create( strPath +
"CExceptionAssert.log" );
IO::Stream^ theFileStreamLog = gcnew IO::FileStream( strPath +
"CExceptionAssert.log", IO::FileMode::Append, IO::FileAccess::Write,
IO::FileShare::Read );
myTextListener = gcnew TextWriterTraceListener( theFileStreamLog
);
Debug::Listeners->Add( myTextListener );
Debug::AutoFlush = true;
//
"("+dt.Day.ToString()+"-"+dt.Hour.ToString()+":"+dt.Minute.ToString()+":"+ dt.Second.ToString()+":"+dt.Millisecond.ToString() +")
"
Debug::WriteLine(m_strLogTimeFormat+" Example:
"+m_strCurrentTimeLogString+"\r\n");

////Create the log that captures the Console statements
//m_MyStreamWriterConsoleWriter = gcnew IO::StreamWriter( strPath
+ "Console.log" );
//m_MyStreamWriterConsoleWriter->AutoFlush = true;
//Console::SetOut( m_MyStreamWriterConsoleWriter ); //capture
normal writes to the console
//Console::SetError( m_MyStreamWriterConsoleWriter ); //capture
normal writes the the
}

static TextWriterTraceListener^ myTextListener;
//static IO::StreamWriter^ m_MyStreamWriterConsoleWriter;
static Threading::ReaderWriterLock theLock;
static property String^ m_strLogTimeFormat {
String^ get() { return "Time Format - <Day of
Month>-<Hour>:<Minute>:<Second>:<Millisecond>";}
}
static property String^ m_strCurrentTimeLogString {
String^ get() {
DateTime dt = DateTime::Now;
TimeZone^ tz = System::TimeZone::CurrentTimeZone;
TimeSpan utcTimeSpan = tz->GetUtcOffset( dt );
String^ Result = String::Format(
"({0:d02}-{1:d02}:{2:d02}:{3:d02}:{4:d03} <{5:d02}:{6:d02} GMT>) "
,dt.Day
,dt.Hour
,dt.Minute
,dt.Second
,dt.Millisecond
,utcTimeSpan.Hours
,utcTimeSpan.Minutes
);
return Result;
}
}
static property String^ m_strCategory {
String^ get() {
#ifdef _DEBUG
return "Assert Thrown";
#endif
return "Exception Thrown";
}
}
};
//################################################## ##########################
//################################################## ##########################
// CConsoleCaptureToLog
// Captures all console output (normal and error) to log file
//################################################## ##########################
//################################################## ##########################

/** Create an instance once to begin capturing the Console output to
log file. */
ref class CConsoleCaptureToLog {
public:
/** Easiest wayto begin capturing the console output to file.
@return - The full path to the file the logging will use. If
Failed, return is NULL (nullptr)
*/
static String^ BeginCapture( void ) {
return BeginOrResetCapture(
Environment::SpecialFolder::CommonApplicationData
,nullptr
);
}

/** If you wish to specify the base starting folder, then fill the
first parameter in. The
Stream Write can be NULL to alow the code to create a good
default stream. Note that
the eTheBaseSpecialFolder param will be ignered if you provide
your own param for the
YourStreamWriter other than NULL (nullptr).*/
static String^ BeginOrResetCapture(
Environment::SpecialFolder eTheBaseSpecialFolder
,IO::StreamWriter^ YourStreamWriter
)
{
String^ strPath = nullptr;
String^ strJustLogFilename = "Console.log";
try
{
//if the stream is already open somewhere else, then close the
old stream first
if (m_MyStreamWriterConsoleWriter != nullptr) {
m_MyStreamWriterConsoleWriter->Flush();
m_MyStreamWriterConsoleWriter->Close();
delete m_MyStreamWriterConsoleWriter;
}

//Gather information for the subdirectory underneath the
specified main special folder
Reflection::AssemblyName TheAssemblyName(
(Reflection::Assembly::GetCallingAssembly())->FullName );
array<wchar_t>^ aBadFilenameChars =
IO::Path::GetInvalidFileNameChars();

// Gather information on the current time/date, and the
assembly information we are
// currently working with to customize the name of the
sub-folders
String^ strDate = (DateTime::Now).ToShortDateString();
String^ strTime = (DateTime::Now).ToLongTimeString();
String^ strAssemblyName = TheAssemblyName.Name;
String^ strAssemblyVersion = "v" + TheAssemblyName.Version;

for each( wchar_t cc in aBadFilenameChars ) {
strTime = strTime->Replace( cc, '_' );
strDate = strDate->Replace( cc, '_' );
strAssemblyName = strAssemblyName->Replace( cc, '_' );
strAssemblyVersion = strAssemblyVersion->Replace( cc, '_' );
}

//Create the general path for the log files
strPath = Environment::GetFolderPath( eTheBaseSpecialFolder )
+ "\\"
+ "Your Company Name"

+ "\\"
+ strAssemblyName
+ "\\"
+ strAssemblyVersion
+ "\\"
+ strDate + " - " + strTime
+ "\\"
;
try{
//Create path for the logging files
IO::Directory::CreateDirectory( strPath );

//Create the log that captures the Console statements but
allows other processes to read the
//file while this creates and appends to it
if(m_MyStreamWriterConsoleWriter == nullptr) {
if( YourStreamWriter != nullptr )
m_MyStreamWriterConsoleWriter = gcnew IO::StreamWriter(
gcnew IO::FileStream(strPath + strJustLogFilename,
IO::FileMode::Append, IO::FileAccess::Write, IO::FileShare::Read) );
m_MyStreamWriterConsoleWriter = YourStreamWriter;
}
//at this point the stream should always be valid! .....dude.
CExceptionAssert::Assert( m_MyStreamWriterConsoleWriter !=
nullptr );
}
catch( Exception^ e) {
CExceptionAssert::Assert( false, "Inner Except:" + e->Message +
"\r\n Failed to Create Directory and file stream for: " + strPath +
"Console.log", e );
return nullptr;
}
m_MyStreamWriterConsoleWriter->AutoFlush = true;
Console::SetOut( m_MyStreamWriterConsoleWriter ); //capture
normal writes to the console
Console::SetError( m_MyStreamWriterConsoleWriter ); //capture
normal writes the the
}
catch( Exception^ e ) {
CExceptionAssert::LogMessageWithStackTrace( "Inner Except:" +
e->Message + "\r\n Failed to start capturing console output to: " +
strPath + "Console.log" );
return nullptr;
}
return strPath + strJustLogFilename;
}

private:
static CConsoleCaptureToLog() {
m_MyStreamWriterConsoleWriter = nullptr;
}
static IO::StreamWriter^ m_MyStreamWriterConsoleWriter;
};

}} // namespace Utility { namespace Share {

{{{{{{{ End File ExceptionAssert.h }}}}}}}}

Tony M

Jun 11 '06 #1
Share this Question
Share on Google+
1 Reply


P: n/a
This is not the right place for sharing code.
If you want to share code, articles etc..., please do so on
www.codeproject.com which was
created for that sole purpose.

--

Kind regards,
Bruno van Dooren
br**********************@hotmail.com
Remove only "_nos_pam"
Jun 11 '06 #2

This discussion thread is closed

Replies have been disabled for this discussion.