views:

696

answers:

3

I keep getting an AccessViolationException when calling the following from an external DLL:

FILES_GetMemoryMapping(MapFile, out size, MapName, out PacketSize, pMapping, out PagePerSector);

Which has a prototype that I've setup as such:

    [DllImport("Files.DLL", SetLastError = true)]
    public static extern uint FILES_GetMemoryMapping(
        [MarshalAs(UnmanagedType.LPStr)]
        string pPathFile,
        out ushort Size,
        [MarshalAs(UnmanagedType.LPStr)]
        string MapName,
        out ushort PacketSize,
        IntPtr pMapping,
        out byte PagesPerSector);

Now, the argument that is causing this is most likely the 5th one (IntPtr pMapping). I've ported this code over from a C++ app into C#. The 5th argument above is a pointer to a struct which also contains a pointer to another struct. Below is how I have these sctructs setup:

    [StructLayout(LayoutKind.Sequential)]
    public struct MappingSector
    {
        [MarshalAs(UnmanagedType.LPStr)]
        public string Name;
        public uint dwStartAddress;
        public uint dwAliasedAddress;
        public uint dwSectorIndex;
        public uint dwSectorSize;
        public byte bSectorType;
        public bool UseForOperation;
        public bool UseForErase;
        public bool UseForUpload;
        public bool UseForWriteProtect;
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct Mapping
    {
        public byte nAlternate;
        [MarshalAs(UnmanagedType.LPStr, SizeConst=260)]
        public string Name;
        public uint NbSectors;
        public IntPtr pSectors;
    }

The C++ equivalent of these are as follows:

typedef struct {
    char*       Name;
    DWORD    dwStartAddress;
    DWORD  dwAliasedAddress;
    DWORD  dwSectorIndex;
    DWORD  dwSectorSize;
    BYTE  bSectorType;
    BOOL  UseForOperation;
    BOOL        UseForErase;
    BOOL        UseForUpload;
    BOOL        UseForWriteProtect;
} MAPPINGSECTOR, *PMAPPINGSECTOR;

typedef struct {
    BYTE   nAlternate;
    char   Name[MAX_PATH]; // MAX_PATH = 260
    DWORD   NbSectors;
    PMAPPINGSECTOR pSectors; 
} MAPPING, *PMAPPING;

I have a feeling I did something wrong with either porting over these structs, or porting over the function prototype. A Marshaling issue of somesort.

The function all the way at the top of this post gets called twice in my code. Once with pMapping set to null (this puts a value in "size"). Memory is then allocated for a new struct using this size parameter and the function is called again now using a pointer to this allocated memory space for pMapping. (pMapping also has a pointer for the other struct which also gets some space allocated during this time).

Here is the old c++ code that accomplished this:

FILES_GetMemoryMapping((LPSTR)(LPCTSTR)MapFile, &Size, (LPSTR)MapName, &PacketSize, pMapping, &PagePerSector);
// Allocate the mapping structure memory
pMapping = (PMAPPING)malloc(sizeof(MAPPING));
pMapping->NbSectors = 0;
pMapping->pSectors = (PMAPPINGSECTOR) malloc((Size) * sizeof(MAPPINGSECTOR));
printf("mapsectorsize: <%d>\n", football);
printf("pMappingsize: <%d>\n", f2);  
// Get the mapping info
FILES_GetMemoryMapping((LPSTR)(LPCTSTR)MapFile, &Size, (LPSTR)(LPCTSTR)MapName, &PacketSize, pMapping, &PagePerSector);

I initially thought I wasn't allocating the correct amount of space so I tried the old C++ code above and found out that:

sizeof(MAPPING) = 272
and
sizeof(PMAPPINGSECTOR) = 40

I did the same check in my C# code and found the following:

Marshal.SizeOf(new Mapping()) = 16
and
Marshal.SizeOF(new MappingSector()) = 40

We got a problem here. The Mapping struct should be of size 272, but its only 16. Thinking I could just do a quick fix, I manually allocated 272 instead of 16 here, but it still errored out with an AccessViolationException.

Any idea on how to fix this? Or what might still be going wrong?

A: 

I haven't worked through all this, I'm afraid, but if you've got structs with 'char*'s in and you're marshalling them as 'string', then you should be careful to be decorating stuff with the appropriate CharSet = CharSet.Ansi attributes.

One thing which would be useful to add to your posting is the C++ prototype for the function (I would not refer to your DLLImport declaration as a 'prototype', but that might just be me.)

Will Dean
+1  A: 

'prototype' was not the correct word, I like "DLLImport declaration" better.

And I've just got it working.

so in C++:

typedef struct {
    BYTE                        nAlternate;
    char                        Name[MAX_PATH]; // MAX_PATH = 260
    DWORD                       NbSectors;
    PMAPPINGSECTOR      pSectors;       
}

to C#:

[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Ansi)]
public struct Mapping
{
    public byte nAlternate;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst=260)]
    public char[] Name;
    public uint NbSectors;
    public IntPtr pSectors;
}

A character Array is NOT a string, and should be treated as an array of characters.... Who would have guessed :P

Nick
+1  A: 

According to MSDN, you should pass a StringBuilder for a fixed-length buffer. Try the following, or some variant (untested):

[StructLayout(LayoutKind.Sequential)]
public struct Mapping
{
    public byte nAlternate;
    [MarshalAs(UnmanagedType.LPStr, SizeConst=260)]
    public StringBuilder Name;
    public uint NbSectors;
    public IntPtr pSectors;

    public Mapping()
    {
        Name = new StringBuilder(259); 
        //This will be a buffer of size 260 (259 chars + '\0')
    }
}
lc