I am writing a scripting engine for my game using the LuaInterface library. I am getting an error when attempting to instantiate the class in Lua. The error is:
"./Scripts/sv_worldgen.lua:2: attempt to call global 'Campfire' (a string value)"
Where sv_worldgen.lua is (in entirety):
function GenerateChunk(worldChunk, chunkGridPosition)
tf = Campfire()
tf:SetPosition(chunkGridPosition)
end
Campfire is a class in C#, and appears to be exposed to lua as per the CLRPackage example and of course the LuaInterface Reference. I cannot seem to get around this error, and I have done due diligence of searching. The only other behavior of the script I can manage throws a similar error, but where it is "(a table value)". What am I doing wrong? Thank you in advance!
I tried explicitly doing Campfire._ctor(), but _ctor() is a string value.
This was resolved by using CLRPackage and using it to first load the assembly.
//Lua
JASG = CLRPackage("JASG", "JASG")
Then and only then can you link the classname to the actual C# class using (this must be done before trying to access it in Lua):
//Lua
Campfire=JASG.Campfire;
and then normal instantiation can occur by
//Lua
cf = Campfire()
Related
I'm fairly new to C# but i'm looking to convert an object to an array of unsigned shorts. The original data is an array of WORD's (numerical value WORD) passed as an object. I've attempted the following but keep getting an error.
object temp = Agent.Port("PumpPressure1_01").Value;
ushort[] PP1_01 = ((IEnumerable)temp).Cast<object>()
.Select(x => x == null ? x.ToUshort())
.ToArray();
When I run this I get the following error:
'System.Collections.Generic.IEnumerable<T>' requires '1' type arguments.
The namespaces I used when I get the above error are:
using System.Linq;
using System.Text; // Don't think this is required but added it in case
If I add the following namespaces:
using System.Collections;
using System.Collections.Generic;
I get the following error.
'System.Linq.ParalleIEnumerable.Select<TTSource,TResult>()' is not supported by the language
I'm sure this is an obvious issue but I've been hunting the net for a while and can't find a solution. My best guess is that the Select function isn't correct as this was originally designed to convert an object to an array of strings.
Any help would be great.
Thanks
IEnumerable is a generic interface, so you have to declare the datatype you are using...
To be honest though, I would want to check what that call to
object temp = Agent.Port("PumpPressure1_01").Value;
is actually returning - by inspecting it in the debugger... If it is simply returning a reference to an array of a numeric type, you should be able to simply cast it. What you are doing though is trying to cast each individual item within the array - I suspect that's not what you should be doing - which would be casting the array itself.
Can you give us any API documentation for the Port method on the Agent object so I can see what it is meant to return? Can you try the inspection and see what that gives you?
Why you casting to IEnumerable and then casting it back to object if your temp variable is already of type object?
Also IEnumerable<T> is a generic interface and must specify exact type (as exception also says to you). If you have an array of integers and you want to work with them it should be IEnumerable<int>
Thanks for all the help and feedback.
Unfortunately I was't paying enough attention to the warnings that was posted which seems to be causing the issue.
Warning: Reference to type 'System.Func '2' claims it is defined in 'c:\Windows\Microsoft.NET\Framework64\v2.0.50727mscorlib.dll'. but it could not be found
It seems that there is some issue with the .NET reference. I have another VM which I tested the following solution on and it seemed to work without issue. Looks like I'll have to reinstall the software package to get it to work on the VM i want to use.
The software package I'm using is a custom package that uses C# to build solutions with prebuilt classes made to look like plug and play blocks. You can connect the blocks together drawings lines from one input/output of a block to another. You can then build C# code inside the blocks. Basically c# for dummy's like me..
Example of the blocks:
As for the code, I did have to make some changes as follows but now works a treat. Agent.Port("PumpPressure1_01").Value.RawValue is used to reference the particular ports on the block.
object temp = (object)Agent.Port("PumpPressure1_01").Value.RawValue;
UInt16[] PP1_01 = ((System.Collections.IEnumerable)temp).Cast<object>()
.Select(x => Convert.ToUInt16(x))
.ToArray();
foreach(UInt16 x in PP1_01)
{
Agent.LogDebug("values: " + x.ToString());
}
Again, thanks for all the help. Just need to resolve the issue with the library reference now.
I have a small c# class, that does some logging stuff and that is called from a powershell scripting framework using:
[System.Reflection.Assembly]::LoadFrom("$ExtensionsPath\LogWriter.dll")
$Log = New-Object LogWriter($LogFile, $addTimeStamp, $logLevel, $overWrite)
Writing into the log file goes like this
$Log.AddInfo("myText")
Works fine so far.
What I am thinking about for some time is, if I am able to use stringexpansion in the AddInfo() method of my LogWriter class?
Look at the example:
$ModulesPath = ‘C:\temp\modules’
$test = ‘This is a text and I want to expand $ModulesPath in my c# LogWriter class’
$Log.AddInfo($test)
The c# class shall now expand the $modulespath in $test as powershell does. I already know that in c# I have access to the powershell runspace from which the c# class was called using System.Management.Automation Namespace. But then I am lost how to really expand the variable.
The entry written into the logfile should look like this:
This is a text and I want to expand C:\temp\modules in my c# LogWriter class
Of course I know I can do this in my script using
$Log.AddInfo(($ExecutionContext.InvokeCommand.ExpandString($test)))
But this is nasty because it looks ugly and if I forget to add this statement no expansion is done.
So I thought of retrieving the current Runspace in my c# class and do the ExpandString-Command there to get the expanded variable but I fail.
This is beyond my knowledge.
Anyone here to tell my if this is possible? I already think of some other tasks where to use this so please do not start a flame war about if this makes sense or not.
Rgds
Jan
How can the value for $ModulesPath be known outside of your script?
If you want it to be expanded in C#, then you have to send it, may be as a second Parameter to AddInfo like:
$Log.AddInfo($test, $ModulesPath)
Now it's known and the replacement could be done by:
string sNew = sTest.Replace("$ModulesPath", sModulesPath);
where sTest and sModulesPath are the parameters.
not sure if this is what you're asking about, but please try using double quotes on the $test string:
$test = "This is a text and I want to expand $ModulesPath in my c# LogWriter class"
I’m trying to pass a COM object from C# code to Perl.
At the moment I’m wrapping my Perl code with PerlNET (PDK 9.4; ActiveState) and I have defined a simple subroutine (+ required pod declaration) in Perl to pass objects from C# to the wrapped Perl module.
It seems that the objects I pass are not recognized correctly as COM objects.
An example:
In C# (.NET 4.0), the ScriptControl is used to load a simple class from a file written in VBScript.
var host = new ScriptControl();
host.Language = "VBScript";
var text = File.ReadAllText("TestScript.vbs");
host.AddCode(text);
dynamic obj = host.Run("GetTestClass");
What I get (obj) is of type System.__ComObject. When I pass it to my Perl/PerlNET assembly and try to call method Xyz() in Perl I get the following (runtime) exception:
Can't locate public method Xyz() for System.__ComObject
If, however, I do more or less the same thing in Perl, it works. (In the following case, passing only the contents of my .vbs file as parameter.)
I can even use the script control :
sub UseScriptControl {
my ($self, $text) = #_;
my $script = Win32::OLE->new('ScriptControl');
$script->{Language} = 'VBScript';
$script->AddCode($text);
my $obj = $script->Run('GetTestClass');
$obj->Xyz();
}
Now, calling Xyz() on obj works fine (using Win32::OLE).
In both cases I use:
use strict;
use Win32;
use Win32::OLE::Variant;
Another approach:
I can invoke methods by using InvokeMember of class System.Type if I specify exactly which overload I want to use and which types I’m passing:
use PerlNET qw(typeof);
typeof($obj)->InvokeMember("Xyz",
PerlNET::enum("System.Reflection.BindingFlags.InvokeMethod"),
PerlNET::null("System.Reflection.Binder"),
$obj,
"System.Object[]"->new());
Using this approach would mean rewriting the whole wrapped Perl module. And using this syntax..
Now I am wondering if I am losing both the advantages of the dynamic keyword in .NET 4.0 and the dynamic characteristics of Perl (with Win32::OLE) by using PerlNET with COM objects.
It seems like my preferred solution boils down to some way of mimicking the behaviour of the dynamic keyword in C#/.NET 4.0.
Or, better, finding some way of converting the passed COM object to something that will be recognized as compatible with Win32::OLE. Maybe extract some information of the __ComObject for it to be identified correctly as COM object.
I have to add that I posted to the PDK discussion site too (but didn’t get any response yet): http://community.activestate.com/node/18247
I also posted it to PerlMonks - as I'm not quite sure if this is more a Perl or C#/.NET question:
http://www.perlmonks.org/?node_id=1146244
I would greatly appreciate any help - or advise on where to look further.
I have discovered the cause of the issue. An answer has been posted below.
EDIT: The problem has changed, please see "The problem" section.
I am using LuaInterface. The generic call for lua functions using this library has this signature LuaFunction.Call(params object[] args). I have created a wrapper function that catches exceptions from the library and formats them for display on the in-game console window.
I am trying to call a lua function, but it is not receiving the arguments. This is the line in C#
Game.Instance.scriptEngine.Call("GenerateChunk", chunks[chunkID], GetChunkGridPosition(chunkID));
Which is simply wrapping a call to this Lua function that accepts two arguments:
//lua
function GenerateChunk(worldChunk, chunkGridPosition)
Log(LogLevel.Error, worldChunk.ToString());
Log(LogLevel.Error, chunkGridPosition.ToString());
end
that merely calls back into a C# Log function (which resolves correctly, and is visible in the Lua context).
The problem is that I am getting an "invalid arguments to method call" error from luainterface when attempting to call the GenerateChunk function, throwing this back:
invalid arguments to method call
at JASG.ScriptEngine.LuaError(Exception ex) Scripting\ScriptEngine.cs:line 144
at JASG.ScriptEngine.Call(String fnName, Object[] args) Scripting\ScriptEngine.cs:line 86
at JASG.ChunkManager.WakeChunk(Int32 chunkID) World\ChunkManager.cs:line 123
at JASG.ChunkManager.GetChunk(Int32 chunkID, Boolean wakeIfAsleep) World\ChunkManager.cs:line 53
I have tried various ways of calling the ScriptEngine.Call method, tried wrapping the arguments in an object[] array, etc., but no dice. Any ideas why lua is not receiving my arguments that I am passing? I have verified both arguments are non-null in C# when being passed in.
I've never used Lua before, but I've seen this kind of strange behaviors with calling COM objects (or any interop), or when the target assembly call is loaded on a different App Domain, or any other technology that intercommunicates a .Net assembly with a non-.Net one.
Have you tried using the [Serializable] attribute on the classes that define the result of "chunks[chunkID]" and "GetChunkGridPosition(chunkID)"? Are all your interop classes and types compatible between both assemblies?
Just thinking out loud here.
Side note: you should reduce your code to the shortest example that produces the problem. For instance, we don't need to see your wrapper function. You should have tried removing it. If that solved the problem, it's an important clue you should have been mentioned. If the problem remained, then that code is just a distracting irrelevancy for anyone reading this.
Your problem could be in your Log function. Everything thing else looks fine, that's the only code we can't actually see, and your problem can be reproduced like this:
public static void Log(int errorLevel, string message)
{
Console.WriteLine(message);
}
public void Test()
{
var lua = new Lua();
lua.RegisterFunction("Log", this, GetType().GetMethod("Log"));
lua.DoString("function foo() Log('a','b') end");
lua.GetFunction("foo").Call();
}
In this case, because 'a' cannot be marshaled into a number.
I incorrectly identified the problem as being with the call into Lua. The error message I was receiving was in fact originating from the Lua script calling back into my C# Log function.
I have discovered the hard way that in spite of exposing the enum LogManager.LogLevel to the lua script envronment, Lua does not support enum types. Thus,
Log(LogLevel.Debug, "hello");
was becoming
Log("Debug", "hello");
when marshalled by LuaInterface for the C# function. It was not until I created an ancillary ScriptLog(string level, string msg) that I was able to properly use the function from within lua. I wanted to keep the functionality of being able to use the enum names within Lua.
NOTE: As Lua does not support enum types, tonumber(LogLevel.Debug) fails as well.
I am working on a game which uses C# and C++. Classes for models are written in C# and levels structure is stored in XML files. When I want read it in C++ and want to build project I have this strange error and I don't where to find some bugs.
Error 1 error C3699: '*' : cannot use this indirection on type 'Cadet::XMLReader::Models::Obstacle' C:\Program Files (x86)\Microsoft Visual Studio 11.0\VC\include\xmemory0 527 1 Cadet.Game
These kind of errors are in xmemory0 and list files? what they are? and it happend only for Obstacle class, the rest are fine.
Here it is part of the code
void SetupObstacles(std::list<Cadet::Game::Entities::Obstacle> &obstacles)
{
int size = CurrentLevel->Obstacles->Length;
Cadet::XMLReader::Models::Obstacle^ currentObstacle;
}
It looks like Cadet::Game::Entities::Obstacle is a managed class (since you've declared currentObstacle as a reference with ^). If that's the case, you can't directly store managed objects in STL containers like std::list<>.
It's hard to say what to do next w/o more context, but one possible fix would be to change your SetupObstacles method:
void SetupObstacles(System::Collections::Generic::List<Cadet::Game::Entities::Obstacle>^ obstacles)
{ ... }
Do you have a pointer to an Obstacle somewhere?
The help on this error suggests that some types (such as trivial properties) cannot have a reference type--you can't have a pointer to it. Try using ^ instead.