View previous topic :: View next topic |
Author |
Message |
Baaleos
Joined: 02 Sep 2007 Posts: 830
|
Posted: Mon Feb 17, 2014 16:05 Post subject: Passing c++ methods/pointers to C# Managed code |
|
|
Hi All,
I am interested to know if there is a way to have say
RunScript and Set/GetLocal* hooked in c++, and then passed to managed c# code.
There is a nwnx dotnet plugin out there, but what I am looking for is a stable way of interacting with the nwn module, from the outside world.
Eg:
In .Net you could spawn a windows form on a separate thread.
And in theory, you could have a button that when clicked, fires the RunScript method in nwn.
In order to do this with nwnx_dotnet, I'd imagine I need to pass a pointer of the RunScript method or something, to the C# environment, from the c++ domain, eg: c++ is used by nwnx to locate the pointer, and then invoke it like an ordinary method call from the c# code.
Does this sound feasible?
What im trying to do, is integrate a .net web application with my PWServer.
Eg: Click on a character, on the server status screen, and kick them, and have them instantly kicked - without needing to use polling scripts / heartbeats. |
|
Back to top |
|
|
Baaleos
Joined: 02 Sep 2007 Posts: 830
|
Posted: Mon Feb 17, 2014 16:13 Post subject: |
|
|
I've seen a website that suggested invoking the RunScript method from a nwnx_* dll with pInvoke.
Eg:
[DllImport("nwnx_cool.dll", SetLastError=true)]
static extern int RunScript(string sString);
Is there special requirements required to make methods within c++ dll's invokable in this methodology?
I hope that if I create a wrapper method in c++ (with the bare minimum amount of c++ required)
and then do the DLLImports, I should be able to call RunScript and other methods with relative ease?
Does this sound possible? |
|
Back to top |
|
|
Baaleos
Joined: 02 Sep 2007 Posts: 830
|
Posted: Mon Feb 17, 2014 16:26 Post subject: |
|
|
Realise Im kinda answering my own posts here....
I've read something about.
Code: |
extern "C" _declspec(dllexport) bool TestFunc()
{
return true;
}
|
If this theory holds out true, then I should be able to create a method called
RunMyScript(string s1, string sID);
And declare it in the same way
Eg
extern "C" _declspec(dllexport) bool RunMyScript(string sScript)
{
CNWSModule *Mod = (*NWN_AppManager)->app_server->srv_internal->GetModule()
(*NWN_VirtualMachine)->Runscript(&CExoString(sScript), Mod->obj_id);
}
Then, in theory, I could do the DLLImport in C#
And call the RunMyScript from the C# Environment.
Can someone confirm this? |
|
Back to top |
|
|
Terra_777
Joined: 27 Jun 2008 Posts: 216 Location: Sweden
|
Posted: Mon Feb 17, 2014 23:44 Post subject: |
|
|
You can't export code to another application since the application that you export too does not run in nwservers memoryspace and as thus would crash the moment you try to call RunScript as it doesnt exist on that address. Instead what you want is to inject code into nwserver (which is what nwnx does for you) in order to reach the methods within nwservers memoryspace.
If two separate application are to communicate you need a medium to communicate though, an example on this is SQL/Database where you got the game exchanging information with the outside using the database which you can then manipulate using PHP for example.
I've never worked with C# but what you want to do is make a C# module (if thats even possible) and have a nwnx plugin import it, then from the nwnx plugin you call a method from your C# module every tick/frame/mainloop whatever to check your events. The C# module would then rely data back to nwnx using the nwnx_plugin as a medium of communications between them.
Another such medium can be network communication where you can establish a connection on the loopback this is what the game does when you connect to the database on a lower level. It'd however require a hook in the mainloop to check for incoming data every tick/frame so basically you'd have to establish your own protocol. IE:
Code: | mainloophook(){
//Check fordata
if(select(socket....)>0){
recv/recvfrom(mydataptr,...)
dealwithdata(mydataptr);
}
} |
However network programming is another tricky minx and would probably require more work then its worth to get working in an acceptable and secure manner. The stuff I did in nwnx cool (the nwnx bridge whatever thingystuffer) was an example of this. However when I did it back then I was clueless so I wouldn't use that code as reference for anything. _________________ I dun have any signature, I'm happy anyway. |
|
Back to top |
|
|
Baaleos
Joined: 02 Sep 2007 Posts: 830
|
Posted: Wed Mar 05, 2014 16:30 Post subject: nwnx_bridge |
|
|
Hey Terra,
I am having a go with your bridge plugin again,
I was just wondering if you can confirm its usage.
I am able to send it messages, and for the most part it seems to be handling them correctly.
However, occasionally when used, it causes the server to hang : no error message or anything, just like it gets stuck in an infinite loop in the code somewhere.
The code I am using to test the bridge is this:
its in C#
Code: |
string sMessage = TextBox1.Text;
TcpClient tcpClient = new TcpClient(new IPEndPoint(IPAddress.Parse("127.0.0.1"),0));
tcpClient.Connect("127.0.0.1",12344);
byte[] bInfo = null;
bInfo = Encoding.UTF8.GetBytes(sMessage);
tcpClient.Client.Send(bInfo);
Array.Clear(bInfo, 0, bInfo.Length);
tcpClient.Client.Receive(bInfo);
TextBox1.Text = Encoding.UTF8.GetString(bInfo);
tcpClient.Close();
|
My log gives me output like this
Quote: |
WSA: Connection accepted: 127.0.0.1
WSA: Data Recv: say Hello
WSA: Connection Lost!
WSA: Offline!
o AddAttackActions: 00005d03
Target: 00005cf6
Arg1: 0
Arg2: 0
Priority: 0
o AddAttackActions: 00005cf7
Target: 00005cf6
Arg1: 0
Arg2: 0
Priority: 0
WSA: Offline!
WSA: Bound socket: 12344!
WSA: Online!
o AddAttackActions: 00005cfa
Target: 00005cf6
Arg1: 0
Arg2: 0
Priority: 0
|
I have my bridge script setup so that when the DC message is received, it restarts the bridge 1 second later, the say command is setup to 'Shout' the message provided in-game:
I chose 1 second just to make sure the socket has had enough time to be disposed of.
Can the 1 second be reduced?
Do I need to disconnect on my clients side, or do I let the server (your socket) disconnect me automatically?
Cheers, |
|
Back to top |
|
|
Terra_777
Joined: 27 Jun 2008 Posts: 216 Location: Sweden
|
Posted: Wed Mar 05, 2014 16:47 Post subject: |
|
|
The server I wrote isnt any good, its a forking server where it forks of connections to new threads instead of being asynchronous. Its not a very good approach at all so its bound to be problems with it. Could create (or help create) a simple plugin that opens a socket and relies all info to nwscript events when I got time. _________________ I dun have any signature, I'm happy anyway. |
|
Back to top |
|
|
Baaleos
Joined: 02 Sep 2007 Posts: 830
|
Posted: Wed Mar 05, 2014 16:54 Post subject: |
|
|
Ah, ok
Well what you've produced with the bridge functionality of nwnx_cool is still good enough for proof of concept ideas: Just means I need to be careful how I use it.
Wish I could help you, but I dont know enough c++ to do socket/networking.
If it could be done in c#, it would be a breeze,
alas - the nwnx interface is c++. |
|
Back to top |
|
|
Terra_777
Joined: 27 Jun 2008 Posts: 216 Location: Sweden
|
|
Back to top |
|
|
Baaleos
Joined: 02 Sep 2007 Posts: 830
|
Posted: Mon Apr 07, 2014 12:30 Post subject: |
|
|
Hi Terra,
Yeah its interesting: I've already got nwnx_dotnet with some ammendments that allow me to invoke external .net dll's programatically.
http://nwnx.org/phpBB2/viewtopic.php?t=1002&start=30
Source code available here.
The only problem with it is that it doesn't allow 2 way communication to nwnserver.
eg: I can create an instance of a c# class from the nwnx plugin, but that class once instantiated, is not able to reach back into the nwnserver domain, and execute c++ methods.
I am sure its possible to do, im just not sure how to do it really.
I've done something similar when working with vb script and c#
in VB Script getRef allows you to get the memory pointer to an address in memory, if that memory points to a method, you can then use Reflection to execute that object as a method, with the desired arguments.
Code: |
private static void ExecuteOnThread(object objRef, int iDelay, string[] args)
{
Thread.Sleep(iDelay);
try
{
objRef.GetType().InvokeMember("", System.Reflection.BindingFlags.InvokeMethod,
null, objRef, args);
}
catch (Exception e)
{ }
}
|
I was rather hoping that there was a way of passing c++ structures as objects into methods, and then executing methods on those objects, from a c# domain using reflection.
eg
in nwnx supply a player object as the object we are setting the local var on,
then we supply the memory location as the location of the method we want to invoke in memory in the var value, and also supply the arguments we want to use with that method in the var as well.
I was rather hoping that the c# could take a memory location, and invoke it with reflection. |
|
Back to top |
|
|
Terra_777
Joined: 27 Jun 2008 Posts: 216 Location: Sweden
|
Posted: Mon Apr 07, 2014 15:42 Post subject: |
|
|
Marshal.GetDelegateForFunctionPointer Method (IntPtr, Type)
Only virtual machine language I've integrated using C++ is lua but C# is also a virtual machine language so somewhere there should be a way to register methods or classes in C#. I think the above function I linked is what you're looking for. _________________ I dun have any signature, I'm happy anyway. |
|
Back to top |
|
|
Baaleos
Joined: 02 Sep 2007 Posts: 830
|
Posted: Mon Apr 07, 2014 16:20 Post subject: |
|
|
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate int MultiplyByTen(int numberToMultiply);
Code: |
static void Main(string[] args)
{
IntPtr pDll = NativeMethods.LoadLibrary(@"PathToYourDll.DLL");
//oh dear, error handling here
//if (pDll == IntPtr.Zero)
IntPtr pAddressOfFunctionToCall = NativeMethods.GetProcAddress(pDll, "MultiplyByTen");
//oh dear, error handling here
//if(pAddressOfFunctionToCall == IntPtr.Zero)
|
MultiplyByTen multiplyByTen = (MultiplyByTen)Marshal.GetDelegateForFunctionPointer(
pAddressOfFunctionToCall,
typeof(MultiplyByTen)); Code: |
int theResult = multiplyByTen(10);
bool result = NativeMethods.FreeLibrary(pDll);
//remaining code here
Console.WriteLine(theResult);
}
|
I assume the bits in bold would be the parts I am interested in?
If my nwnx_dotnet plugin is running within the nwnserver process appdomain / protection level - then in theory, getting hold of its method as a virtual function should be as simple as feeding in an IntPtr which equals the uint of the memory offset?
Or would I need to add the offset to the process base address?
As for the delegate bit highlighted, I imagine that this is going to be the harness for my method - so it needs to match the signature of the nwnserver method? |
|
Back to top |
|
|
Terra_777
Joined: 27 Jun 2008 Posts: 216 Location: Sweden
|
|
Back to top |
|
|
Baaleos
Joined: 02 Sep 2007 Posts: 830
|
Posted: Mon Apr 07, 2014 22:26 Post subject: |
|
|
Thanks for the help Terra,
I was wondering if you might know if the Marshal class also allows me to return struct/objects from memory, as well as the methods based on their location/pointer?
Code: |
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
private delegate int RunScriptDelegate(object objVirtualMachineThis, string Script, UInt32 objID, int a4);
public static void testMashalling()
{
// CVirtualMachine **NWN_VirtualMachine = (CVirtualMachine**)0x0066C048;
// int (__thiscall *CVirtualMachine__RunScript)(CVirtualMachine *pTHIS, CExoString *a2, nwn_objid_t, int a4) = (int (__thiscall*)(CVirtualMachine *pTHIS, CExoString *a2, nwn_objid_t, int a4))0x005BF9D0;
IntPtr runScript = (IntPtr)0x005BF9D0;
RunScriptDelegate runTheScript = (RunScriptDelegate)Marshal.GetDelegateForFunctionPointer(runScript, typeof(RunScriptDelegate));
runTheScript(null, "dll_test", 0x7fffff,1);
}
|
The problem I have is that im not entirely sure how to retrieve an instance of the CVirtualMachine - Which is the 'this' in the RunScript method.
My End goal is to be able to fire a script from the c# code.
In the c++ code for cool, I see that a 'static' (im guessing its static?)
instance of the VirtualMachine class is available for use within the project.
Code: |
CVirtualMachine **NWN_VirtualMachine = (CVirtualMachine**)0x0066C048;
|
Im just wondering if there is a way for me to grab hold of it in C# and use it as the first argument in the RunScript method, without having to define its structure.
Eg: Pass it in as an object (generic) and let nwn handle the rest?
The reason I want to try and avoid defining the class/structs is because then it leads to having to define CExoStrings and all sorts of interconnected types.
Does this seem feasible?
I am seeing
Code: |
Marshal.PtrToStructure
|
But this takes the type as an argument, im assuming that it wont return the full object unless it knows how much memory to allocate for it based on its type.
:-\ |
|
Back to top |
|
|
Terra_777
Joined: 27 Jun 2008 Posts: 216 Location: Sweden
|
Posted: Tue Apr 08, 2014 8:03 Post subject: |
|
|
No, you still have to write a function in C++
ie:
Code: |
C++
static int __stdcall CSharpRunScript( void * CSharpObj, void * sScript ){
<whatever stuff neasesary here blablabla>
(*CVirtualMachine)->RunScript( sScript, 0 );
} |
Then you pass that function as your marshal. Don't pass the nwn classes, look at the stack overflow example I linked above. _________________ I dun have any signature, I'm happy anyway. |
|
Back to top |
|
|
Baaleos
Joined: 02 Sep 2007 Posts: 830
|
Posted: Tue Apr 08, 2014 10:41 Post subject: |
|
|
Code: |
static int __stdcall CSharpRunScript(void * CSharpObj, char* sScript){
(*NWN_VirtualMachine)->Runscript(&CExoString(sScript), 0);
}
|
I am attempting to put this in the NWNXCool.cpp - and although its not complaining, im just wondering should the first argument be of type void?
RunScript requires the script name, as well as the object id to run on.
Did you add in CSharpObj for a specific purpose?
Whats the constant object id for the module object?
Isnt it something like 0x7fffff ?
No worries, refactored to use
Code: |
static int __stdcall CSharpRunScript(void * CSharpObj, char* sScript){
(*NWN_VirtualMachine)->Runscript(&CExoString(sScript), (*NWN_AppManager)->app_server->srv_internal->GetModule()->obj_id);
}
|
|
|
Back to top |
|
|
|
|
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
|