PInvoke - 读取字符串字段的值 - “尝试读取或写入受保护的内存”

Posted

技术标签:

【中文标题】PInvoke - 读取字符串字段的值 - “尝试读取或写入受保护的内存”【英文标题】:PInvoke - reading the value of a string field - "Attempted to read or write protected memory" 【发布时间】:2009-10-22 13:52:10 【问题描述】:

我在访问 COM 接口中的某些字符串字段时遇到问题。调用整数字段不会导致错误。尝试调用 clientID()deviceID()key() 时,我收到旧的“尝试读取或写入受保护的内存” 错误。

接口源代码如下:(代码来源于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();
;

下面是访问接口的C#代码:

[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();

关于为什么简单地尝试读取字段会引发访问冲突的任何建议?

【问题讨论】:

接口有成员lastFetched、lastModified等,如PRUint32。我不确定那是什么,但它似乎是 32 位的,但您在 c# 代码中使用 Int64。这可能是个问题吗? 我进行了更改,但遇到了同样的异常:( C++代码导出了哪些字符串? 【参考方案1】:

此接口中的字符串是 C 样式字符串 (char*) 的变体,但 COM Interop 默认将字符串视为 BSTR。您让编组器尝试读取错误类型的字符串,然后使用 CoTask 内存分配器将其释放,因此您遇到访问冲突也就不足为奇了。如果您的字符串是 [In] 参数,您可以使用适当的 MarshalAs 属性来装饰它们,但这不适用于返回值参数。因此,您需要将它们编组为 IntPtrs,然后手动编组并释放底层内存。

我会尝试以下方法:

[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();

正如克里斯上面提到的,PRUint32s 实际上是 32 位而不是 64 位无符号整数,所以我已经更改了它们。另外,我已经将方法更改为属性,因为这可以更好地捕获 idl 的含义,因为它们都是只读的,它实际上并不影响布局。

clientID 和 deviceID 的字符串可以使用 Marshal.PtrToStrAnsi 读取,如下所示:

        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);

您是使用 NS_Free 还是内存接口来释放字符串取决于您如何使用整个 xpcom 设置。键值是一个抽象字符串,因此它的表示取决于它的来源。看起来它通常可以像其他字符串一样被视为指向 ANSI 字符串的指针。

我没有设置可以为您尝试任何这些,并且文档至少可以说有些不透明,因此您可能需要对此进行一些调整。

【讨论】:

你理应得到 +150 的赏金!请允许我添加我的 +1,我正在处理 C++ DLL 中的(非 COM)遗留代码,它从非托管 -> 托管代码传递了一个标准的 stl c_str()。一切都在 32 位环境下运行良好,但在 64 位环境下就坏了。你的解决方案成功了。谢谢!!【参考方案2】:

如果将 [return: MarshalAs(UnmanagedType.BStr)] 语句应用于 clientID 和 deviceID 会怎样?

【讨论】:

以上是关于PInvoke - 读取字符串字段的值 - “尝试读取或写入受保护的内存”的主要内容,如果未能解决你的问题,请参考以下文章

PInvoke“试图读取或写入受保护的内存”

PInvoke - 如何表示来自 COM 接口的字段

Javascript没有从输入字段中读取单个字符

HDF5 C# pinvoke 读取数据集名称列表

C#用啥方法可以读取以下字符串各个字段的值?(特急!)

当它们包含在现有字段的值内时读取键和值