Jump to content

  • Log In with Google      Sign In   
  • Create Account

Nypyren

Member Since 19 Aug 2002
Offline Last Active Yesterday, 10:28 PM

#5289172 I Have a Problem in "char *" & "LPCWSTR"

Posted by Nypyren on 28 April 2016 - 06:47 PM

The W in LPCWSTR stands for "Wide" and usually if you see errors about that it means you're mixing Unicode and Multibyte strings in your program due to incorrect settings or incorrect string literals.

You can either try making everything consistently Unicode (put L prefixes on all of your string literals or the _T macro like you've already tried) or consistently Multibyte (open your project properties and change "Character Set" to "Use Multibyte Character Set".

If that doesn't help then the problem is much deeper and you might need to post more code.

New VS2015 C++ windows projects default to Unicode, I believe. Older tutorials like NeHe probably assume the older multibyte default.


#5289153 Network Library that supporst RPCs

Posted by Nypyren on 28 April 2016 - 04:32 PM

For most types, the sender and receivers both know how big they are based on agreed-upon information. You typically do NOT need to send the size of every field.

For example, you could write code that agrees that all 32-bit integers are 4 bytes. Or you could agree to write them using SQLite4 varint encoding, or VLQ encoding, or whatever.

Let's look at a bare-bones BinaryReader/BinaryWriter pair in C#:
 
public class Foo
{
    public int A;
    public int B;

    public void Read(BinaryReader reader)
    {
        A = reader.ReadInt32();
        B = reader.ReadInt32();
    }

    public void Write(BinaryWriter writer)
    {
        writer.Write(A);
        writer.Write(B);
    }
}
The Read and Write functions don't need to write bytes indicating that A is 4 bytes and B is 4 bytes, because it's implicit.


You only really need lengths for two main things:

1. Knowing how much data to wait for in your network receiver before you attempt to deserialize the whole message.
2. Skipping fields of unknown types if you have a protocol that allows for different client versions to talk to each other. You probably won't need this, though.


For RPC, you could do something like this (very barebones example):
 
public void HandleRPC(BinaryReader reader)
{
    var function = (RPCFunction)reader.ReadInt32(); // RPCFunction being an enum

    switch (function)
    {
        case RPCFunction.ConsoleWriteLine:
        {
            // This is a bit of a security risk if you were to do this in C++, but in C# the worst you'll get is an exception.
            var formatString = reader.ReadString(); // Internally it has the string length first
            var numArgs = reader.ReadInt32();
            object[] args = new object[numArgs];
            for (int i=0; i<numArgs; ++i)
                args[i] = ReadPolymorphicType(reader);
            
            Console.WriteLine(formatString, args);
        }

        // Other functions read their expected parameters, then call the appropriate function in the same way.
    }
}

public object ReadPolymorphicType(BinaryReader reader)
{
    int typeId = reader.ReadInt32();
    switch (typeId)
    {
        case 0: return null;
        case 1: return reader.ReadByte();
        case 2: return reader.ReadInt32();
        case 3: return reader.ReadSingle();
        case 4: return reader.ReadDouble();
        case 5: return reader.ReadString();
        case 6: { var foo = new Foo(); foo.Read(reader); return foo; }
        // etc.
    }
}
This is a manually-written, tedious way to go about it, but it serves as an example for how you can serialize/deserialize data. You would ideally want to use whatever is most convenient for your project.

I would personally use varints for all integers, have string backreference support, etc.


#5289111 Network Library that supporst RPCs

Posted by Nypyren on 28 April 2016 - 11:14 AM

Definitely put the size first. It's so much easier than worrying about delimiters, EOM indicators and dealing with escaping other data somehow.

Really the only nuisance part of doing size first is you have to build up your whole message in a temporary buffer first, then measure its length immediately before sending. But that's not nearly as bad as dealing with escaping data (which inevitably leads to bugs).

If you use a binary format you don't have to worry about any kind of string escaping or delimiters. Your data is arranged in consistent blocks with the leading information letting you know exactly how much data comes next, either using lengths, or type IDs, or any other deterministic approach.


#5288967 Network Library that supporst RPCs

Posted by Nypyren on 27 April 2016 - 01:25 PM

You can use delegates instead of MethodInfo, I think:

https://msdn.microsoft.com/en-us/library/53cz7sc6(v=vs.110).aspx


#5288957 Network Library that supporst RPCs

Posted by Nypyren on 27 April 2016 - 12:27 PM

Why do they have to be in the same order? Isn't it somehow possible to use a dictionary that contains the allowed rpc methods and gives me the method via the methodname as a string? Don't know if this is possible with delegates, but I've read reflection isn't really a performant way of doing this, I could use reflection only once at the program start to get all the allowed rpc functions and save them in a dictionary with methodname (string ) -> method ( delegate or something ). This way I can get the method in O(1) time and invoke it with the parameters...
 
Another thing I thought about was that the rpcmessage won't contain the methodname, but rather a number ( or a hash value or something ) that corresponds to a method ( it's also less data sent ), so when someone is looking into the packets, that are sent, he doesn't exactly know the methodname. The client however has to know those numbers then and since my code should be readable I have to create an enumeration or something so I still know which method is represented and I have to manually add to this enumeration if I add an rpc function. I guess I don't like to manually do stuff, when I think they can be automatically done, so the server has to provide some sort of interface for the rpc methods... but that's another topic....


If you use strings, it's O(1)... but you still have to transmit the whole string over the network (which will be larger than an integer), and the Dictionary<string,MethodInfo> will use GetHashCode on the string when looking up the dictionary entry. GetHashCode depends on the length of the string - it's pretty fast, but if you use integers you don't need it at all.

If you use integers, you can make a List<MethodInfo> instead and have an O(1) lookup with no hashing, and slightly less memory use than the Dictionary (the size of your function table will be insignificant compared to texture memory though, so don't worry about that).

You would still need a way on the sending side to find the function's ID before you know what integer to send over the network. In the first case you could reuse the same Dictionary and just send functions by name as well. In the second case you can look up the ID in any way you see fit (A Dictionary<string,int> for example).

In both cases, you would search for your functions once at startup and put them into the collection(s). After that you would use your function ID (either string or integer) to pick the method from the collection. If you use integers, you just need to make sure every client/server has the same IDs for the same functions. That's what I meant by the "in the same order" thing above.

Generally I only use Dictionary when the keys are sparse. If I have a Dictionary where the keys are all integers from 0 to N, I just use a List or possibly even an array instead.

MethodInfo aren't very fast to invoke, either. If you had a hardcoded switch statement, it would be much faster. Although since the RPC already has the delay of being transferred over the network, a MethodInfo.Invoke probably won't factor into performance noticably.


#5288945 Network Library that supporst RPCs

Posted by Nypyren on 27 April 2016 - 11:43 AM

I thought of maybe using attributes to declare a function as an RPC method, or to have a naming convention, so only methods with a name, that starts with "RPC" will be invoked.


Using attributes is a good way to do it. You just need to make sure that the way you scan for attributes always keeps the results in the same order. You could search for everything that has the attribute, and then alphabetically sort the search results so that they're always guaranteed to be in the same order.


#5288860 Network Library that supporst RPCs

Posted by Nypyren on 26 April 2016 - 09:10 PM

For a more compact serialization, you should consider using a series of bytes instead of strings. For more fancy ways to make your network messages smaller, look at the encoding tricks that protobuf and SQLite4 use:

https://developers.google.com/protocol-buffers/docs/encoding
https://sqlite.org/src4/doc/trunk/www/varint.wiki

To handle more complex types, you write functions to read/write those complex types. For example with a list, you write the number of items in the list as a varint (-1 for null if you care about distinguishing between null and empty lists), followed by the serialized form of each element in the list. For a Dictionary<K,V> you send the number of pairs as a varint, then alternate between key,value,key,value.

For polymorphism you make a table of type IDs and send the type ID as a varint immediately before any polymorphic object.

For arbitrary graphs of objects, you traverse the graph, adding each object to a list the first time you see it, until you've seen all reachable objects. Then you write: # of objects, the type IDs for each object in the list, followed by the fields for each object in the list. Any time you encounter a field which is a reference type, you find that object in the list and serialize its index (you can use a Dictionary<object,int> to speed this up). Deserialization creates the object list, news each object by its type id, then fills in their fields and cross-references, then returns the first entry in the list as the "root" of the graph.


If you pass a function name over the network and use reflection to invoke it, it's a security risk because someone could send a network message like [mscorlib.dll:System.IO.Directory.Delete, "C:/Users", true] over the network, and you would *never* want to let your receiving side execute that.

To handle the issue of looking up the functions from method names, I suggest making part of your program startup register all of the possible functions you might want to use. Then, when you want to RPC that function, you look up its index in that table and send just the index over the network. Assuming both ends built the function table the same way, the receiver will know which function to use as well.


#5288855 Network Library that supporst RPCs

Posted by Nypyren on 26 April 2016 - 06:57 PM

The only "magic" part of UNET is that they replace the body of the sender's RPC method with a replacement that performs parameter serialization and sends the call, and generate the receiver-side deserialization code to call the function for you.

To do RPC manually, you just need to:

- Serialize parameters and function context (if applicable, like the gameObject's unique ID or whatever)
- Send the serialized information over the network like any other network data
- On the receiving end, deserialize the parameters and then call the appropriate function in the appropriate context (if applicable) using lookup tables (or reflection - it's debatable whether this is OK or not in the context of RPC - you maintain less code with reflection anyway).

If you want to handle returning a value back to the caller, you need an additional ID for the call itself, and a lookup table of code to call when you get a response. Since Unity doesn't have C# 5.0 await support yet, I would use a lambda-style continuation.


#5288531 Noob pointer question

Posted by Nypyren on 24 April 2016 - 11:06 PM

Based on 1st case, are 'a' and 'pA' themselves go to a stack memory for storing values after deletion? I mean, after deleting the memory they are pointing to, they themselves are still there... pointing to zero or whatever value isn't it?


Yes, a variable's value is stored somewhere as well (for a pointer, that value is the integer representing the memory address).

Variable storage depends on how it's declared:

Local variables and function parameters: Stored on the stack and/or in registers. The compiler decides based on how you use the variable and what optimizations it can do.

struct/class member fields: Stored as part of the struct/class (i.e. on the stack if it's a local variable or on the heap if you 'new' it).

Global/static variables: Stored in a non-stack, non-heap region of memory that is cleaned up when the program exits. You aren't allowed to 'delete' memory in this region.

Thread static variables: Stored in thread-local-storage. You aren't allowed to 'delete' memory in this region, either.


#5288479 Noob pointer question

Posted by Nypyren on 24 April 2016 - 12:54 PM

The short answer is: Only 'delete' what you 'new'.

The longer answer is:

You have to think about pointers separately from the memory they point to.

'delete' does not delete the pointer, it's deleting the memory the pointer points to. This also means that even if you have two pointers pointing to the same memory, you're only allowed to delete once.

Your second case allocates 'a' on the stack. pA points to that stack memory. You never need to 'delete' something on the stack, because stack memory gets cleaned up automatically when the function returns.


#5288080 How to deal with this unexpected counter-offer?

Posted by Nypyren on 21 April 2016 - 10:25 PM

Based on the reaction from the directors and president, I think you should also discuss things (everything!) with them a lot more. Be honest about what you like and don't like, discuss the things that you're interested in and what would make you happiest, and be tactful and open minded.

I think this will give everyone more information that helps guide your decision making process better.


Are there any big showstoppers you can focus on? Are you 'crumbling from a thousand tiny stress fractures'? Solving even the small nagging nonsense can improve job happiness a lot.

Personally I would stay where you are unless you just can't stand it.


#5287815 Object reference not set to an instance of an object

Posted by Nypyren on 20 April 2016 - 01:19 PM

Planet[i] = new PlanetDetails();
You can only skip that step if you're using an array of value types (int, bool, float, enum, struct - basically everything that's not a class).

When you make an array, its entries are all set to the "default" value of the corresponding type.

int: 0
float: 0.0f
double: 0.0
all the other numeric and enum types = whatever their concept of 0 is.
bool: false
class: null
struct: non-null, each individual field = its own default

In this case you have a class, so the default is null; You need to individually 'new' each element in the array.


#5287483 "Timer undeclared identifier" Is my compiler drunk? o.o

Posted by Nypyren on 18 April 2016 - 10:53 AM

Perhaps a mutual #include? Try this:
 
//#include "Timer.h"
class Timer;
Move the #include to your CPP file (or your PCH, if applicable).


#5287112 c++ should nested switch statements be avoided?

Posted by Nypyren on 15 April 2016 - 04:21 PM

(sword example)


I wouldn't make derived classes just to change values. I would have a data table of different kinds of swords, without any separate classes.

 
Of course not. I was merely giving an object-oriented example using C++. Were I to give a more practical demonstration, it would use an unnecessary amount of screen space for what was being conveyed.


I understand; although a newcomer won't! We have to be careful with what our demonstrations and examples teach to new programmers. In most of the derived class examples I see that are designed to show an idea to new programmers, it's REALLY often a case where an experienced programmer would say "Nooooo~, an experienced programmer would never do this!". The problem is that the newcomer will see the example and believe that it should be done that way. They don't yet know what good code vs. bad code looks like yet, and they don't know what all of the available options are for solving different problems.

I think that examples are ideal if they can be made so they are both understandable by new programmers and something that an experienced programmer would not object to. We can improve the entire programming community if we're careful with our examples to avoid misunderstanding.


#5287056 c++ should nested switch statements be avoided?

Posted by Nypyren on 15 April 2016 - 10:51 AM

(sword example)


I wouldn't make derived classes just to change values. I would have a data table of different kinds of swords, without any separate classes.




PARTNERS