views:

272

answers:

2

I'm having trouble accessing the some string fields in a COM interface. Calling the integer fields does not result in an error. When attempting call clientID(), deviceID() or key(), I get the old "Attempted to read or write protected memory" error.

Here is the source interface code: (code sourced from here)

[scriptable, uuid(fab51c92-95c3-4468-b317-7de4d7588254)]
interface nsICacheEntryInfo : nsISupports
{
    readonly attribute string  clientID;
    readonly attribute string deviceID;
    readonly attribute ACString key;
    readonly attribute long  fetchCount;
    readonly attribute PRUint32  lastFetched;
    readonly attribute PRUint32  lastModified;
    readonly attribute PRUint32  expirationTime;
    readonly attribute unsigned long  dataSize;
    boolean  isStreamBased();
};

Here is the C# code for accessing the interface:

[Guid("fab51c92-95c3-4468-b317-7de4d7588254"), ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
interface nsICacheEntryInfo
{
    string clientID();
    string deviceID();
    nsACString key();
    int fetchCount();
    Int64 lastFetched();
    Int64 lastModified();
    Int64 expirationTime();
    uint dataSize();
    [return: MarshalAs(UnmanagedType.Bool)]
    bool isStreamBased();
}

Any suggestions as to why simply trying to read a field should throw access violations at me?

A: 

What if you apply the [return: MarshalAs(UnmanagedType.BStr)] statememnt to clientID and deviceID ?

hjb417
Thanks, it didn't help though.
Nathan Ridley
+2  A: 

The strings in this interface are variants on C style strings (char*'s) but COM Interop by default treats strings as BSTRs. You have the marshaller trying to read the wrong kind of string and then free it with the CoTask memory allocator, so it's no surprise you get an access violation. If your strings were [In] parameters you could just adorn them with the appropriate MarshalAs attribute but that won't work with return value parameters. So you need to marshal them as IntPtrs and then manually marshal and free the underlying memory.

I would try the following:

[Guid("fab51c92-95c3-4468-b317-7de4d7588254"), ComImport, 
    InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
interface nsICacheEntryInfo
{
    IntPtr clientID { get; }
    IntPtr deviceID { get; }
    IntPtr key { get; }
    int fetchCount { get; }
    uint lastFetched { get; }
    uint lastModified { get; }
    uint expirationTime { get; }
    uint dataSize { get; }
    [return: MarshalAs(UnmanagedType.Bool)]
    bool isStreamBased();
}

As Chris mentioned above the PRUint32s are actually 32 bit not 64 bit unsigned integers so I've changed them. Also, I've changed the methods to properties since that captures the meaning of the idl better, since they are all readonly it doesn't actually affect the layout.

The strings for clientID and deviceID can be read using Marshal.PtrToStrAnsi as so:

        nsIMemory memoryManagerInstance = /*maybe get this from somewhere*/;
        nsICacheEntryInfo cacheEntryInstance = /*definitely get this from somewhere*/;
        IntPtr pClientID = cacheEntryInstance.clientID;
        string clientID = Marshal.PtrToStringAnsi(pClientID);

        NS_Free(pClientID);

        //or

        memoryManagerInstance.free(pClientID);

Whether you use NS_Free or the memory interface to free the strings depends on how you are using the whole xpcom setup. The key value is an abstract string so its representation depends on where it comes from. It appears though that it can usually be treated as a pointer to an ANSI string like the others.

I don't have the setup to try any of this for you and the documentation is, to say the least, somewhat opaque so you may need to do a little tweaking on this.

Stephen Martin