views:

343

answers:

5

I am marshalling data between a C# and C++ application. In the C# application, I force the size of a string to be some size (say, 256 bytes). I would like to read in that exact same amount in C++ (I will be recreating the structs with reinterpret_cast) so that the data will remain formatted as it was in the C# application. Unfortunately, I'm pretty rusty with C++ and I'm not sure how to force a string's size in a struct in C++.

As requested, an example. I have a struct in C# that looks like this:

[StructLayout(LayoutKind.Sequential)]
        public struct DataLocater
        {
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)]
            public string filename;
            [MarshalAs(UnmanagedType.I4)]
            public Int32 sizeOfData;
            public Int32 startLocation;
            public Int32 encrypted;
        }

Which I am marshalling several of (along with other data) to a data file. The C++ file is then reading this file in and I will be parsing it back into struct in C++ with the same structure. My first attempt at this struct looked like:

struct DataLocater
{
 std::string filename;
 int sizeOfData;
 int startLocation;
 int encrypted;
};

But there is no way for the compiler to know that I want that std::string to be 256 bytes.

edit: adding full header file for example.

#pragma once
#include "CoreArea/Singleton.h"

class FileReader : public Singleton<FileReader>
{
    friend class Singleton<FileReader>;

public:
    void* GetFileData(std::wstring fileName, int &size);

private:
    FileReader();
    ~FileReader();

    struct Header
    {
     std::string somestring;
     int numOfFiles;
    };

    struct DataLocater
    {
     char[256] filename;
     int sizeOfData;
     int startLocation;
     int encrypted;
    };
};
A: 

Provide any piece of code at all, and we will consider helping.

But without even a fragment of code, this is a "write-my-code-for-me" question.

abelenky
Code example provided.
Chris
okay, you added some structure definitions. I note that you didn't actually add any instructions. Nor did you add a question.
abelenky
This should be a comment, not an answer...
Georg Fritzsche
feeling a little pedantic are we?
Byron Whitlock
+6  A: 

Is this what you are looking for?

struct DataLocater
{
        char filename[256];
        int sizeOfData;
        int startLocation;
        int encrypted;
};

edit: Consider that you don't NEED to force either string to be of a certain size, you could declare filename as char * and on the C# size you'd use a string with no restrictions. This way you'd only use as much memory as needed (and possibly use more than you initially estimated, avoiding buffer overrun errors).

Blindy
This definitely seemed logical and was my first attempt. However, it threw a whole slew of errors at me, the first being: error C3409: empty attribute block is not allowed. I assumed at this point, that you could not give it a size in the struct. I require a set size to make reading files easier, and as part of the specs.
Chris
Just tested it in VS2008 and it compiled without a problem. Try showing exactly what you're trying to compile (the entire thing) so we can see your error.
Blindy
Interesting. I've added the full header file's contents to the bottom of the post.
Chris
+6  A: 

In general you're taking the wrong approach here. Mixing non-data only C++ types and PInvoke will lead you to a lot of pain. Types like std::string should not be used in this scenario because the marshaller cannot correctly create them at runtime.

Instead you need to use more primitive types. In this particular scenario, based on the managed signature, the appropriate C struct is the following

struct DataLocater
{
    char filename[256];
    int sizeOfData;
    int startLocation;
    int encrypted;
};
JaredPar
@Downvoter, care for a comment?
JaredPar
as noted above, declaring char[256] yields an "empty attribute block is not allowed" error. This was my initial reasoning for trying my hand at std::string. Good advice on mixing types, though.
Chris
@Chris, i'm not sure why that is happening but it's unrelated to the signature. The DataLocator struct is valid C++ code (verified on VS2008 RTM)
JaredPar
What does char[256] filename net you versus char filename[256]? That block won't compile in g++ - gives "error: expected unqualified-id before '[' token".
Will Bickford
That's really weird, because it's definitely compiling using string instead of char[]. i've included the full contents of the header file in the post.
Chris
@Will I can not believe that slipped by me. I feel like a fool, hahah. Thank you!
Chris
+2  A: 

You can't marshal C# arrays directly into a std::string. The string class has a whole bunch of other stuff in its memory space besides the raw character data: vtable pointer, length specifier, probably a bunch of other stuff too.

You need to specify the filename parameter as a wchar[256], and then later convert the wchar[256] into a std::string.

Kennet Belenky
+1  A: 

A std::string isn't fixed length, and typically doesn't store its data in its allocated space but in the heap. You want a fixed length array of characters or bytes, depending on the structure's charset.

Pruning the examples on MSDN for ByValTStr use gives these three:

// C 
struct StringInfoA {
   char      f2[256];
};
struct StringInfoW {
   WCHAR     f2[256];
};
struct StringInfoT {
   TCHAR     f2[256];
};

Which correspond to

// C#
[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Ansi)]
struct StringInfoA {
   [MarshalAs(UnmanagedType.ByValTStr, SizeConst=256)] public String f2;
}

[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)]
struct StringInfoW {
   [MarshalAs(UnmanagedType.ByValTStr, SizeConst=256)] public String f2;
}

[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Auto)]
struct StringInfoT {
   [MarshalAs(UnmanagedType.ByValTStr, SizeConst=256)] public String f2;
}
Pete Kirkham