logo logo

 Back to main page

The NWNX Community Forum

 FAQFAQ   SearchSearch   MemberlistMemberlist   UsergroupsUsergroups   RegisterRegister 
 ProfileProfile   Log in to check your private messagesLog in to check your private messages   Log inLog in 
 
Yeah!!! NwnX2 with .NET
Goto page Previous  1, 2, 3  Next
 
Post new topic   Reply to topic    nwnx.org Forum Index -> Windows development
View previous topic :: View next topic  
Author Message
ronchese



Joined: 30 Dec 2007
Posts: 30

PostPosted: Sun Sep 16, 2012 20:28    Post subject: Reply with quote

It is working for me. Have you checked the included sample module?

I've enhanced the calling code for better undestanding, see below:

Code:

    //test: gets a GUID
    SendMessageToPC(GetFirstPC(), " ");
    SendMessageToPC(GetFirstPC(), " ");
    SendMessageToPC(GetFirstPC(), GetGuid());

    //test: gets the date from the machine
    SendMessageToPC(GetFirstPC(), " ");
    SendMessageToPC(GetFirstPC(), " ");
    SendMessageToPC(GetFirstPC(), "Current date from server: " + GetDate());


    //test: the plugin support ' quotes
    SendMessageToPC(GetFirstPC(), " ");
    SendMessageToPC(GetFirstPC(), " ");
    FloatingTextStringOnCreature("Floating: This is a 'MESSAGE' with '' quotes", GetFirstPC());
    SendMessageToPC(GetFirstPC(), "Quotes testing: " + DotNetExecute("Test", "This is a 'MESSAGE' with '' quotes"));

    //test: the plugin does not support {&qt} sequence string
    SendMessageToPC(GetFirstPC(), " ");
    SendMessageToPC(GetFirstPC(), " ");
    SendMessageToPC(GetFirstPC(), "Quotes error testing: " + DotNetExecute("Test", "This is a message with encoded {&qt} quotes"));

    //test: execute a method named 'Test' on your .net assembly (see NwnxAssembly assembly, on Execute class)
    SendMessageToPC(GetFirstPC(), " ");
    SendMessageToPC(GetFirstPC(), " ");
    SendMessageToPC(GetFirstPC(), "Method overloaded with 5 parameters: " + DotNetExecute("Test", "Param", "Param2", "Param3", "Param4", "Param5"));


Notice that the code is saying that {&qt} will give an error, and that is intended. =)
Back to top
View user's profile Send private message
Lea_Andersteen



Joined: 15 Sep 2012
Posts: 5

PostPosted: Mon Sep 17, 2012 17:30    Post subject: Reply with quote

Thanks, i will try it when i'm coming back to home Smile
Back to top
View user's profile Send private message
Lea_Andersteen



Joined: 15 Sep 2012
Posts: 5

PostPosted: Mon Sep 17, 2012 21:37    Post subject: Reply with quote

i just done this:
- install nwnx
- put the two DLLs (nwnx_dotnetplugin.dll and NwnxAssembly.dll) into NWN folder;
- launch with demo module frome nwnx-dotnetplugin directory from ur archive.

You tried it with witch dot net Framework version? os?
Back to top
View user's profile Send private message
ronchese



Joined: 30 Dec 2007
Posts: 30

PostPosted: Tue Sep 18, 2012 2:19    Post subject: Reply with quote

Check this out:

https://skydrive.live.com/redir?resid=DA0992CEC3A785CF!364&authkey=!ACNgJeVsyjRrN3o


Contains:
- a setup that would install the correct vc++ redist
- all dlls to save in nwn folder

Working on Win XP (sp2 and sp3), .Net Framework 3.5 sp1
Back to top
View user's profile Send private message
Baaleos



Joined: 02 Sep 2007
Posts: 830

PostPosted: Mon Oct 22, 2012 0:06    Post subject: Reply with quote

* Loading plugins...
* Plugin areas is loaded.
* Plugin chat is loaded.
* Plugin cool is loaded.
* Plugin dmactions is loaded.
* An error occured while loading extension dotnetplugin (14001: This application has failed to start because the application configuration is incorrect. Reinstalling the application may fix this problem. )
* Plugin fixes is loaded.
* Plugin funcs is loaded.
* Plugin leto is loaded.
* Plugin odbc is loaded.
* Plugin resetplugin is loaded.
* Plugin resman is loaded.
* Plugin systemdata2 is loaded.
* Plugin tmi is loaded.
* NWNX2 activated.



any ideas?
Installed the vc++ redist
Back to top
View user's profile Send private message
Baaleos



Joined: 02 Sep 2007
Posts: 830

PostPosted: Mon Oct 22, 2012 1:31    Post subject: Reply with quote

Managed to get it working in the end.

Im using a c# converted nwnxassembly.dll

Essentially, it looks like the nwnxDotNet dll plugin was originally compiled with debug libraries - which are not necessarilly on all server machines.
(Discovered this with Dependancy Walker)

That being said - some of it might have been my fault for compiling with vs2010 after the fact too.

Compiling in release mode was giving errors at runtime about managed code or msil stuff being executed at creation time of dll main.

Cant rememebr the error for sure, but I resolved it by adding the following in the dotnetplugin.cpp.

This as far as I know, tells it to basically stay unmanaged code, until the dllMain function has finished, and then switch over to managed code, which of course, is what we need for our lovely dotNet dlls to work.
Code:


[b]#pragma unmanaged[/b]
BOOL APIENTRY DllMain (HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{
    return TRUE;
}
[b]#pragma managed[/b]



Tested this in game, and my DLL is now able to send e-mails from in-game.

The function, in case anyone is interested (in c# is this)

Code:

using System;
using System.Collections.Generic;
//using System.Linq;
using System.Text;
using System.Collections;
using System.Net;
using System.Net.Mail;
using System.Net.Mime;
using System.Security;

namespace NwnxAssembly
{
    public static class EMailServices
    {

        public static string SendMail(string sAddress, string sSubject, string sContent, string sFromAddress, string sFromName)
        {
           
            try
            {


                MailMessage message = new System.Net.Mail.MailMessage();
                message.BodyEncoding = Encoding.ASCII;

                message.To.Add(sAddress);


                message.Subject = sSubject;
                message.From = new System.Net.Mail.MailAddress(sFromAddress, sFromName);

                message.Body = sContent;
               
                SmtpClient smtp = new System.Net.Mail.SmtpClient("mail.azmodan.net");
                smtp.Credentials = new NetworkCredential("yourlogin", "yourpassword", "yourdomain");
                smtp.UseDefaultCredentials = true;

                using (AlternateView altView = AlternateView.CreateAlternateViewFromString(sContent, new ContentType(MediaTypeNames.Text.Html)))
                {

                    message.AlternateViews.Add(altView);
                    smtp.Send(message);
                }

                //Thread.Sleep(2000);

                message = null;
                smtp = null;

                return "Email Sent";
            }
            catch (Exception e)
            {

                return "Email Failed:"+e.ToString();
            }

        }

    }
}



which is called from the Execution class via this addition

Code:

        public string SendMail(string sAddress, string sSubject, string sContent, string sFromAddress, string sFromName)
        {
            return EMailServices.SendMail(sAddress, sSubject, sContent, sFromAddress, sFromName);
        }



Which is then accessible from nwnscript via

Code:

string SendMail(string sAddress, string sSubject, string sContent, string sFromAddress, string sFromName)
{
                return DotNetExecute("SendMail", sAddress, sSubject, sContent, sFromAddress, sFromName);
}

SendMail("baaleos@myDomain.com", "A Test Mail", "This is a test", "Timmy@Heaven.org", "AngelTimmy");
Back to top
View user's profile Send private message
ronchese



Joined: 30 Dec 2007
Posts: 30

PostPosted: Mon Oct 22, 2012 6:41    Post subject: Reply with quote

Good job!

In a given point of time I had that error too. But the #pragma stuff did not work for me, and I resolved it from other way I can't remember now. Smile

Very nice!
Back to top
View user's profile Send private message
Baaleos



Joined: 02 Sep 2007
Posts: 830

PostPosted: Mon Oct 22, 2012 11:01    Post subject: Reply with quote

While I was trying to get my c# version to work, I encountered a few errors.


1. The Debug Assemblies from vc++ were not included when built.
(I think the one you have in the zip package may be a debug build - Maybe Im wrong?)
Means it will only work on a machine/server that has visual studio installed.

2. The dll when run through nwnx - would give the R6003 message or something like that.
This was caused when running the dll which was built with the Common Runtime Languges flag, as well as the /MD flag for multithreaded dll.
Unfortunately, the Common Runtime Languages flag looked to be needed - otherwise accessing managed code was no allowed - the solution wouldnt build.

For me - the solution in visual studio 2010 was to enable the CRL, the /MD and then use pragma unmanaged, followed by pragma managed.


Now that I have a working nwnxassembly.dll and a working nwnx_dotnet.dll

I am going to work on building the plugin functionality into the nwnxassembly.dll

The current limitation of this dotnet dll is that the nwnxassembly is actually just an include file - that is included at build time, and then required from that point on.

Im hoping to create a system where the dll's can be developed independantly, and added to the system without requiring them present at the build time of nwnx_dotnet.
(Which should be possible via this)

Code:


private void LoadPlugins()
        {
            IsLoadingPlugins = true;
            Thread.Sleep(100);
            string path = System.Windows.Forms.Application.StartupPath;
            string[] pluginFiles = Directory.GetFiles(path, "*.dll");
           
            ipi = new IPlugin[pluginFiles.Length];

            for (int i = 0; i < pluginFiles.Length; i++)
            {
                string args = pluginFiles[i].Substring(
                    pluginFiles[i].LastIndexOf("\\") + 1,
                    pluginFiles[i].IndexOf(".dll") -
                    pluginFiles[i].LastIndexOf("\\") - 1);

                Type ObjType = null;
                //IPlugin ipi;
                // load the dll
                try
                {
                    // load it
                    Assembly ass = null;
                    ass = Assembly.Load(args);
                    if (ass != null)
                    {
                        ObjType = ass.GetType(args + ".PlugIn");
                    }
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.Message);
                    WriteLogMsg(ex.ToString());
                }
                try
                {
                   
                    if (ObjType != null)
                    {
                        ipi[i] = HelperFunctions.CreatePlugin(ObjType);
                        SetHostOnOtherThread(ipi[i]); //While the function is suggesting it is another thread.
                        //I opted to use a linear thread approach - to ensure that the 'IsLoadingPlugins' variable remains useful.
                    }
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.Message);
                    WriteLogMsg(ex.ToString());
                }

                //            Start(assemname);
            }
            IsLoadingPlugins = false;           
        }





Executing a method within the plugin when loaded is similar to how you have done it.

Except that we dont reference the namespace directly, we instead reference the held reference to the plugin's namespace.

Code:

public object SendMessageToPlugin(string sPluginID, object[] oArgs)
        {
            if (oArgs.Length < 1)
            {
                return null;
            }

            IPlugin PluginToUse = (IPlugin)LoadedPlugins[sPluginID];
            if (PluginToUse == null) //Not Got that Plugin -Come back later
            {
                return null;
            }
            return PluginToUse.ExecuteCommand(oArgs);
            //return true;
        }




And then inside the plugin it has a receiving command.

Code:


public object ExecuteCommand(object[] oArgs)
        {
            try
            {
                string sCommand = oArgs[0].ToString();
                Type t = typeof(TypeWhereTheMethodExists);
                MethodInfo m = t.GetMethod(sCommand);
                object[] oArgs2 = new object[oArgs.Length - 1];
                int i = 0;
                int i2 = 1;
                for (i2 = 1; i2 < oArgs.Length; i2++)
                {
                    oArgs2[i] = oArgs[i2];
                    i++;
                }
                return m.Invoke(TypeWhereTheMethodExists, oArgs2);
               
            }
            catch (Exception e)
            {
                GetHost().WriteOutput("Exception:" + e.ToString());
            }
            return null;
        }

Back to top
View user's profile Send private message
ronchese



Joined: 30 Dec 2007
Posts: 30

PostPosted: Mon Oct 22, 2012 14:16    Post subject: Reply with quote

Well, I dont have VS installed on my test VM. Smile

Also, as a suggestion, what if instead plug-ins, you develop a CSharpCodeProvider version?

This way you could write/fix c# "scripts" on-the-fly and without locking dlls.
Back to top
View user's profile Send private message
Baaleos



Joined: 02 Sep 2007
Posts: 830

PostPosted: Mon Oct 22, 2012 17:01    Post subject: Reply with quote

You mean something that can turn common text into a compiled c# assembly, run it, and then return the result?

Heres one I made earlier.
I've been thinking about doing something like that.
It could be a case of creating a new folder in nwn folder, call it something like

cClasses or something

Then put text files that have the c# code in them, and then when the DotNetAssembly calls the appropriate class, and method, this code would then compile the text file, run the method, return the result, and voila - no compiled assemblies needed.

Code:

public object ExecuteCode(string code,string namespacename, string classname,string functionname, bool isstatic, params object[] args)
        {
            object returnval = null;
            Assembly asm = BuildAssembly(code);
            object instance = null;
            Type type = null;
            if (isstatic)
            {
                type = asm.GetType(namespacename + "." + classname);
            }
            else
            {
                instance = asm.CreateInstance(namespacename + "." + classname);
                type = instance.GetType();
            }
            MethodInfo method = type.GetMethod(functionname);
            returnval = method.Invoke(instance, args);
            return returnval;
        }





        private Assembly BuildAssembly(string code)
        {
            Microsoft.CSharp.CSharpCodeProvider provider =
               new CSharpCodeProvider();
            ICodeCompiler compiler = provider.CreateCompiler();
            CompilerParameters compilerparams = new CompilerParameters();
            compilerparams.GenerateExecutable = false;
            compilerparams.GenerateInMemory = true;
           Assembly[] refAssemblies = AppDomain.CurrentDomain.GetAssemblies();
           string[] sSplit = { "," };
           foreach (Assembly a in refAssemblies)
           {
               compilerparams.ReferencedAssemblies.Add(a.Location);
           }
            CompilerResults results =
               compiler.CompileAssemblyFromSource(compilerparams, code);
            if (results.Errors.HasErrors)
            {
                StringBuilder errors = new StringBuilder("Compiler Errors :\r\n");
                foreach (CompilerError error in results.Errors)
                {
                    errors.AppendFormat("Line {0},{1}\t: {2}\n",
                           error.Line, error.Column, error.ErrorText);
                }
                throw new Exception(errors.ToString());
            }
            else
            {
                return results.CompiledAssembly;
            }
        }



I did this particular code/function for a remote agent at work - We had a lab of 100+ lab machines, and managing them could be problematic.

So I developed a C# Remote Agent, and a Client app that could send messages to them all.

I would send uncompiled c# script, then the remote agent would compile it, run it, and send me the response back.

It never actually occured to me that I could use it for nwn though.
Cheers.


Im thinking I could do something like

nwn\CClasses\EmailServices.cs
nwn\CClasses\ForumIntegration.cs

Each could contain methods for things like

1. Sending E-mails to individuals
2. Sending E-mails to Groups
3. Forum Integration - Directly inserting data via mySQL or WebRequests into a forum to integrate community forums with the game. (done this before with mySQL and CronJobs - but this is more direct)
Back to top
View user's profile Send private message
Baaleos



Joined: 02 Sep 2007
Posts: 830

PostPosted: Mon Oct 22, 2012 21:48    Post subject: Reply with quote

For anyone interested in this setup

This goes inside the DotNetFunc_inc.nss
Code:

string RunClassMethod(string sClassName, string sMethodName, string sArgs)
{
    return DotNetExecute("ExecuteClassCode", sClassName,sMethodName,sArgs);
}



This Class goes inside nwnxAssembly c# Project/VB Project (depending on which you are developing in)
Code:

using System;
using System.Collections.Generic;
using System.Text;
using System.CodeDom.Compiler;
using System.CodeDom;
using System.Reflection;
using Microsoft.CSharp;
using System.IO;

namespace NwnxAssembly
{
    public static class CompilerServices
    {
        private const string ClassesFolder = "CSharpClasses";
        public static object ExecuteCode(string codeSourceFile, string namespacename, string classname, string functionname, bool isstatic, params object[] args)
        {
            object returnval = "Null";
            try
            {
                string sPath = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);
                if (!File.Exists(sPath + "\\" + ClassesFolder + "\\" + codeSourceFile))
                {
                    return @"Class/Path Not Found";
                }

                TextReader tReader = new StreamReader(sPath + "//" + ClassesFolder + "//" + codeSourceFile);
                string sAllText = tReader.ReadToEnd();
                tReader.Close();
                tReader = null;



                Assembly asm = BuildAssembly(sAllText);
                object instance = null;
                Type type = null;
                if (isstatic)
                {
                    type = asm.GetType(namespacename + "." + classname);
                }
                else
                {
                    instance = asm.CreateInstance(namespacename + "." + classname);
                    type = instance.GetType();
                }
                MethodInfo method = type.GetMethod(functionname);
                returnval = method.Invoke(instance, args);
                instance = null;
                return returnval;
            }
            catch (Exception e)
            {
                return "Exception:" + e.ToString();
            }
        }





        private static Assembly BuildAssembly(string code)
        {
            Microsoft.CSharp.CSharpCodeProvider provider =
               new CSharpCodeProvider();
            ICodeCompiler compiler = provider.CreateCompiler();
            CompilerParameters compilerparams = new CompilerParameters();
            compilerparams.GenerateExecutable = false;
            compilerparams.GenerateInMemory = true;
            Assembly[] refAssemblies = AppDomain.CurrentDomain.GetAssemblies();
            string[] sSplit = { "," };
            foreach (Assembly a in refAssemblies)
            {
                compilerparams.ReferencedAssemblies.Add(a.Location);
            }
            CompilerResults results =
               compiler.CompileAssemblyFromSource(compilerparams, code);
            if (results.Errors.HasErrors)
            {
                StringBuilder errors = new StringBuilder("Compiler Errors :\r\n");
                foreach (CompilerError error in results.Errors)
                {
                    errors.AppendFormat("Line {0},{1}\t: {2}\n",
                           error.Line, error.Column, error.ErrorText);
                }
                throw new Exception(errors.ToString());
            }
            else
            {
                return results.CompiledAssembly;
            }
        }



    }
}


Add this to Execution.cs inside the same assembly
Code:


//Arguments must be delimitated via ¬ characters
        public string ExecuteClassCode(string sClassFile, string sMethodName, string sArgs)
        {
            string[] sSplitter = { "¬" };
            string[] Arguments = sArgs.Split(sSplitter, StringSplitOptions.RemoveEmptyEntries);
            try
            {
                return CompilerServices.ExecuteCode(sClassFile + ".cs", "NwnxAssembly", sClassFile, sMethodName, true, Arguments).ToString();
            }
            catch (Exception e)
            {
                return "Exception:" + e.ToString();
            }
        }



Then just add uncompiled Classes to c:\nwn\CSharpClasses or whatever your server path structure is.

I've tested this so far with my EmailServices class - and it works.

I called the following from nwnscript;
Code:


 void main()
{

    RunClassMethod("EMailServices", "SendMail", "baaleos@azmodan.net¬A Test EMail¬This Is the Second Test¬Baaleos@azmodan.net¬Jonny");

}


And received the email a few seconds later.
Seems stable enough, hasnt crashed yet.
Back to top
View user's profile Send private message
ronchese



Joined: 30 Dec 2007
Posts: 30

PostPosted: Tue Oct 23, 2012 13:54    Post subject: Reply with quote

Wow! You are really working on it. Very Happy

Edit: I would suggest you one thing more: caching!
Back to top
View user's profile Send private message
Baaleos



Joined: 02 Sep 2007
Posts: 830

PostPosted: Tue Oct 23, 2012 14:14    Post subject: could be do-able Reply with quote

I suppose caching can be done.

The obvious hurdle is to identify when the code has changed, and to disregard the cached version.


im thinking

1. Create List of Assemblies (this is where our compiled assemblies will go (in memory))

2. When a request is received, compare the md5 hash of the class file from the last time a request was made, with the current md5 hash of the class file as it exists in the subfolder.
If they are the same - the class hasnt changed - use the assembly from the list.
If they have changed - the class is different, re-compile, overwrite the stored assembly with the new one (caching it), and then call the method and return as normal.
Back to top
View user's profile Send private message
ronchese



Joined: 30 Dec 2007
Posts: 30

PostPosted: Tue Oct 23, 2012 15:25    Post subject: Reply with quote

I would go one of these ways:

-------------------------------
1. System.IO.FileInfo
Code:

private SortedList<string, DateTime> _cachedDates = new SortedList<string, DateTime>();

//...
//...

var fi = new FileInfo("path");
if (fi.LastWriteTime > _cachedDates["key"]) RefreshCache("key");

Once it will be quicker than performing a hash.

-------------------------------

Or
2. System.IO.FileSystemWatcher

This way you can immediately invoke the compiler whenever the file changes.


.
Back to top
View user's profile Send private message
Baaleos



Joined: 02 Sep 2007
Posts: 830

PostPosted: Tue Oct 23, 2012 21:28    Post subject: Have you ever Reply with quote

I know that nwnx is done in c++
But have you ever tried calling the embedded nwn functions from c#?

I have some source code for a Diablo 2 trainer, that shows how it was done in c#.
I can sorta see how it could be implimented for nwn.

Code:


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
using Codex.Runtime;
using Codex.Diablo.Types;

namespace Codex.Diablo.Modules
{
    public class D2Client : Module
    {
        public static D2Client Module = new D2Client(Kernel32.GetModuleHandle("d2client"));

        #region Delegates

        [UnmanagedFunctionPointer(CallingConvention.StdCall, CharSet = CharSet.Unicode)]
        private delegate void GamePrintDelegate(String text, Int32 color);

        #endregion

        #region Offsets

        private const UInt32 Offset_PlayerUnit = 0x11C3D0;
        private const UInt32 Offset_GamePrint = 0x71740;

        #endregion

        public D2Client(IntPtr baseAddress)
            : base(baseAddress)
        {
            GamePrintFunction = GetDelegateFromOffset<GamePrintDelegate>(Offset_GamePrint);
        }

        private GamePrintDelegate GamePrintFunction;

        public UnitAny PlayerUnit
        {
            get { return ReadPointer<UnitAny>(Offset_PlayerUnit); }
            set { WritePointer(Offset_PlayerUnit, value); }
        }

        public void GamePrint(Int32 color, String format, params object[] args)
        {
            GamePrint(color, String.Format(format, args));
        }

        public void GamePrint(Int32 color, String message)
        {
            GamePrintFunction(message, color);
        }
    }
}


The GetDelegateFromOffset function looks to be key/integral.
It comes in 3 flavors.

Code:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;

namespace Codex.Runtime
{
    public class Module : Structure
    {
        public Module(IntPtr baseAddress)
            : base(baseAddress)
        {

        }

        #region Methods

        protected T GetDelegateFromOffset<T>(UInt32 offset) where T : class
        {
            return Marshal.GetDelegateForFunctionPointer(
                GetOffset(offset),
                typeof(T)) as T;
        }

        protected T GetDelegateFromName<T>(String function) where T : class
        {
            return Marshal.GetDelegateForFunctionPointer(
                Kernel32.GetProcAddress(BaseAddress, function),
                typeof(T)) as T;
        }

        protected T GetDelegateFromOrdinal<T>(UInt16 ordinal) where T : class
        {
            return Marshal.GetDelegateForFunctionPointer(
                Kernel32.GetProcAddress(BaseAddress, ordinal),
                typeof(T)) as T;
        }

        #endregion
    }
}



Does this sort of framework look feasible for nwn server in c#?
Back to top
View user's profile Send private message
Display posts from previous:   
Post new topic   Reply to topic    nwnx.org Forum Index -> Windows development All times are GMT + 2 Hours
Goto page Previous  1, 2, 3  Next
Page 2 of 3

 
Jump to:  
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum


Powered by phpBB © 2001, 2005 phpBB Group