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

本文关键字:Invoke 调用 帮助 XP Windows 诊断 失败 仅在 | 更新日期: 2023-09-27 18:10:00

我定义了以下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大师,所以不要想当然。这个定义方式有什么明显的错误吗?是否还有更多封送处理要进行(手动(?我觉得我可能错过了一些显而易见的东西。

EDIT:根据要求,这里分别是.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#代码。

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

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

C代码中没有可见的#pragma包,本地代码中的结构包实际上是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进行强制转换,因为这就是这些类型的使用。