繁体   English   中英

需要帮助仅在Windows XP上诊断失败的P / Invoke呼叫

[英]Need Help Diagnosing a Failing P/Invoke Call Only on Windows XP

我定义了以下P / Invoke:

[DllImport("helper.dll", CallingConvention=CallingConvention.Cdecl, 
    CharSet=CharSet.Ansi, EntryPoint="F_GetValue")]
private static extern Int32 _F_GetValue(String Formula, ref DATA_STRUCT Data,
    ref DATA_KEY DefaultKeyBuf, ref Double Result);

此调用在Windows Vista及更高版本上成功,但在具有此内存异常的Windows XP上失败:

A first chance exception of type 'System.AccessViolationException' occurred 
in helper.dll

我尝试将前两个“ref”修饰符更改为[In,Out],但这并没有解决问题。

DATA_STRUCT和DATA_KEY都是实例化和预填充的结构体。

这是我调用的C ++方法定义:

int F_GetValue(const char* pFormula, DATA_STRUCT* pData, 
    DATA_KEY* pDefaultKeyBuf, double* freturn)

我不是P / invoke guru,所以不要假设任何事情。 这个定义的方式有什么明显的错误吗? 是否有更多的编组(手动)? 我觉得我可能会遗漏一些明显的东西。

编辑:根据要求,这里是.NET中的结构定义,C ++ F_GetValue()方法和C ++结构定义:

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]
public struct DATA_STRUCT
{
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
    public String DataDir;
    [MarshalAs(UnmanagedType.U2)]
    public UInt16 LTType;
    [MarshalAs(UnmanagedType.U2)]
    public UInt16 FOMType;
    [MarshalAs(UnmanagedType.U2)]
    public UInt16 ResultType;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 136)] // ( POS_BLOCK_SIZE + sizeof(int) + 4 )
    public Byte[] posBlock;
    public DATA_REC dataBuf;
    [MarshalAs(UnmanagedType.U2)]
    public UInt16 dataLen;
    [MarshalAs(UnmanagedType.U2)]
    public UInt16 keyNum;
    public DATA_KEY keyBuf;
    [MarshalAs(UnmanagedType.U2)]
    public UInt16 TNTC;
    [MarshalAs(UnmanagedType.I2)]
    public Int16 status;
}

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]
public struct DATA_KEY
{
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 17)]
    public String LocName;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 17)]
    public String ParName;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 13)]
    public String DateTime;
}

int F_GetValue(const char* pFormula, DATA_STRUCT* pData, DATA_KEY* pDefaultKeyBuf, double* freturn)
{

    if (pFormula[0] == 0) // return quickly if nothing to do
    {
        *freturn = blank;
        pData->ResultType = sbit(DATA_BLANK);
        pData->status = B_NO_ERROR;
        return 0;
    }

    if ((_strnicmp(pFormula, "DOTNET_", 7) == 0) || (_strnicmp(pFormula, "(DOTNET_", 7) == 0)) // switch to/from dotnet
    {
        dotnetCalcs = (_strnicmp(pFormula + 7 + ((pFormula[0] == '(') ? 1 : 0), "ON", 2) == 0);
        *freturn = dotnetCalcs ? 1 : 0;
        pData->ResultType = sbit(DATA);
        pData->status = B_NO_ERROR;
        return strlen(pFormula);
    }

    BOOL bComingFromDotNet = (pData->dataLen == 65535);
    if (dotnetCalcs && (!bComingFromDotNet))
    {
        return F_GetValue2(pFormula, pData, pDefaultKeyBuf, freturn);
    }

    if (pSharedMem->bClient && ! bServer)
    {
        if (FromServer(ACTION_OPEN,NULL) == B_NO_ERROR)
        {
            ((CS_FORMULA *)pSharedMem->ClientServer)->nRecords = 1;
            strcpy(((CS_FORMULA *)pSharedMem->ClientServer)->DataDir,pData->DataDir);
            memcpy(&((CS_FORMULA *)pSharedMem->ClientServer)->Formula[0].DefaultKeyBuf,pDefaultKeyBuf,sizeof(DATA_KEY));
            strcpy(((CS_FORMULA *)pSharedMem->ClientServer)->Formula[0].Formula,pFormula);
            ((CS_FORMULA *)pSharedMem->ClientServer)->iType = CSTYPE_FORMULA;
            ((CS_FORMULA *)pSharedMem->ClientServer)->iAction = ACTION_READ;
            if (FromServer(ACTION_READ,NULL) == B_NO_ERROR)
            {
                *freturn = ((CS_FORMULA_RESULT *)pSharedMem->ClientServer)->Data[0].Data;
                pData->ResultType = ((CS_FORMULA_RESULT *)pSharedMem->ClientServer)->Data[0].ResultType;
                pData->status = ((CS_FORMULA_RESULT *)pSharedMem->ClientServer)->Data[0].status;

                FromServer(ACTION_CLOSE,NULL);
                return strlen(pFormula);
            }
            FromServer(ACTION_CLOSE,NULL);
        }
        *freturn = blank;
        return 0;
    }
    else
    {
        BOOL bOpenTemporaryData = bComingFromDotNet || (pData->dataLen == 0);
        if (bOpenTemporaryData)
        {
            DataOpenAndInitialize(pData,NULL);
        }

        int iReturn = F_DoGetValue(pFormula,pData,pDefaultKeyBuf,freturn);

        if (bOpenTemporaryData)
            DataExec(B_CLOSE,pData);

        return iReturn;
    }
}

typedef struct
{
    char DataDir[MAX_PATH];
    unsigned short LTType; 
    unsigned short FOMType; 
    unsigned short ResultType; 
    BTI_BYTE posBlock[POS_BLOCK_SIZE_];
    DATA_REC dataBuf;
    BTI_WORD dataLen;
    BTI_WORD keyNum;
    DATA_KEY keyBuf;
    unsigned short TNTC;
    BTI_SINT status;
} DATA_STRUCT;

typedef struct
{
char    LocName[LP_SIZE];
char    ParName[LP_SIZE];
char    DateTime[13];
} DATA_KEY;

为了完整起见,我还包括这个方法F_GetValue2(),它在F_GetValue()中调用。 虽然这看起来好像可能会重新运行到托管代码中,但事实并非如此。 这个方法存在于不同的目的,我无法向你保证,在XP问题的情况下它不会被调用,因为它需要(dotnetCalcs &&(!bComingFromDotNet))为真,这是不可能的。

还有一件事,在那里调用的另一个方法是F_DoGetValue(),然后将参数集传递给它。 这种方法很大,所以我不会在这里发布。 但足以说它解析Formula并使用它学到的东西来调用更多使用解析后的字符串从数据库中获取数据的方法,返回Double成员fReturn备份链直到它最终被传递回通过编组的C#代码。

   [StructLayout(..., Pack = 1)]

在C代码中没有#pragma pack可见,本机代码中的结构打包实际为1的几率很低。 默认值为8,[StructLayout]的默认值相同。 最小的健全性检查是,C#中结构类型的Marshal.SizeOf()返回与C代码中的sizeof()完全相同的值。 当不匹配并且确实可能​​存在随机AV时,它将无法正常工作。

并使用调试器来诊断AV。 Project + Properties,Debug选项卡,勾选非托管代码调试选项,以便您可以调试C#和C代码。 在C函数的第一个语句上设置断点。 并检查传递的结构指针的调试视图是否与您在C#代码中指定的数据匹配。 麻烦通常位于结构的末端附近。 Debug + Exceptions,勾选Win32异常的抛出框,让调试器在AV异常发生时停止。

最后,问题证明与证据建议完全不同(特别是考虑到我对P / Invoke的了解程度)。 事实上,问题是使用此声明的结果:

__declspec( thread ) BOOL dotnetCalcs = FALSE;

进入代码后,我发现它在这一行上失败了:

if (dotnetCalcs && (!bComingFromDotNet))

成员“dotnetCalcs”被声明为线程本地存储,经过一些研究后,它似乎是XP上已知的失败。 我发现的一个示例线索是此MSDN页面末尾的注释:

http://msdn.microsoft.com/en-us/library/9w1sdazb(v=vs.80).aspx

有关延迟加载的部分适用于此实例,因为有问题的DLL由于DllImport而被加载。

感谢所有回复的人,我很抱歉带着鹅追逐。 但最终,设置一个特殊调试站的麻烦证明是值得的。

修复:

用调用TLS方法替换__declspec(线程)方法。 在DllMain()中,我建立一个TLS索引并全局保存,使用TlsSetValue()设置初始值。 然后,所有后续请求者都使用该索引通过TlsGetValue()检索TLS值。 只要值发生变化,只需再次使用TlsGetValue()来设置值。 永远记得使用LPVOID进行强制转换,因为这是这些使用的类型。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM