简体   繁体   English

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

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

I have the following P/Invoke defined: 我定义了以下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);

This call succeeds on Windows Vista and later, but fails on Windows XP with this memory exception: 此调用在Windows Vista及更高版本上成功,但在具有此内存异常的Windows XP上失败:

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

I tried changing the first two "ref" modifiers to be [In, Out] but that did not solve the problem. 我尝试将前两个“ref”修饰符更改为[In,Out],但这并没有解决问题。

DATA_STRUCT and DATA_KEY are both structs that are instantiated and pre-populated. DATA_STRUCT和DATA_KEY都是实例化和预填充的结构体。

Here is the C++ method definition that I am calling: 这是我调用的C ++方法定义:

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

I'm no P/invoke guru, so don't assume anything. 我不是P / invoke guru,所以不要假设任何事情。 Is there anything obviously wrong with the way this is defined? 这个定义的方式有什么明显的错误吗? Is there more marshaling to be done (manually)? 是否有更多的编组(手动)? I feel like I might be missing something obvious. 我觉得我可能会遗漏一些明显的东西。

EDIT: as requested, here are the structure definitions in .NET, the C++ F_GetValue() method, and the C++ structure definitions, respectively: 编辑:根据要求,这里是.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;

For the sake of completeness, I also include this method, F_GetValue2(), which is called in F_GetValue(). 为了完整起见,我还包括这个方法F_GetValue2(),它在F_GetValue()中调用。 And while this looks as though it might be running right back into managed code, it won't. 虽然这看起来好像可能会重新运行到托管代码中,但事实并非如此。 This method exists for a different purpose, and I cannot assure you that it is not getting called in the case of my XP woes, as it would require that (dotnetCalcs && (!bComingFromDotNet)) be true, which it would not be. 这个方法存在于不同的目的,我无法向你保证,在XP问题的情况下它不会被调用,因为它需要(dotnetCalcs &&(!bComingFromDotNet))为真,这是不可能的。

One more thing, the other method that is called in there is F_DoGetValue(), and the set of parameters is then passed on to it. 还有一件事,在那里调用的另一个方法是F_DoGetValue(),然后将参数集传递给它。 That method is huge, so I'll not post it here. 这种方法很大,所以我不会在这里发布。 But suffice it to say that it parses Formula and uses what it learns to call yet more methods that use the parsed string(s) to fetch data from the database, returning the Double member fReturn back up the chain until it ultimately is delivered back to the C# code via the marshaling. 但足以说它解析Formula并使用它学到的东西来调用更多使用解析后的字符串从数据库中获取数据的方法,返回Double成员fReturn备份链直到它最终被传递回通过编组的C#代码。

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

There is no #pragma pack visible in the C code, odds that the structure packing in your native code is actually 1 are low. 在C代码中没有#pragma pack可见,本机代码中的结构打包实际为1的几率很低。 The default is 8, same default for [StructLayout]. 默认值为8,[StructLayout]的默认值相同。 A minimum sanity check is that Marshal.SizeOf() on your structure types in C# returns the exact same value as sizeof() in your C code. 最小的健全性检查是,C#中结构类型的Marshal.SizeOf()返回与C代码中的sizeof()完全相同的值。 It will not work correctly when there's a mismatch and a random AV is indeed likely. 当不匹配并且确实可能​​存在随机AV时,它将无法正常工作。

And use the debugger to diagnose the AV. 并使用调试器来诊断AV。 Project + Properties, Debug tab, tick the unmanaged code debugging option so you can debug both your C# and your C code. Project + Properties,Debug选项卡,勾选非托管代码调试选项,以便您可以调试C#和C代码。 Set a breakpoint on first statement in the C function. 在C函数的第一个语句上设置断点。 And check that the debug view of the passed structure pointers matches the data you assigned in your C# code. 并检查传递的结构指针的调试视图是否与您在C#代码中指定的数据匹配。 Trouble is usually located near the end of the structure. 麻烦通常位于结构的末端附近。 Debug + Exceptions, tick the thrown box for Win32 exceptions to let the debugger stop when the AV exception occurs. Debug + Exceptions,勾选Win32异常的抛出框,让调试器在AV异常发生时停止。

In the end, the problem turned out to be quite different from what evidence suggested (especially given my level of knowledge regarding P/Invoke). 最后,问题证明与证据建议完全不同(特别是考虑到我对P / Invoke的了解程度)。 In fact, the problem was the result of using this declaration: 事实上,问题是使用此声明的结果:

__declspec( thread ) BOOL dotnetCalcs = FALSE;

After stepping into the code, I found that it failed on this line: 进入代码后,我发现它在这一行上失败了:

if (dotnetCalcs && (!bComingFromDotNet))

The member "dotnetCalcs" is declared as thread local storage, and after doing some research, it appears that it is a known fail on XP. 成员“dotnetCalcs”被声明为线程本地存储,经过一些研究后,它似乎是XP上已知的失败。 One example clue I found was the comment at the end of this MSDN page: 我发现的一个示例线索是此MSDN页面末尾的注释:

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

The part about delayed load is applicable in this instance, as the DLL in question is being loaded due to the DllImport. 有关延迟加载的部分适用于此实例,因为有问题的DLL由于DllImport而被加载。

Thank you to all who responded, and I'm sorry for having led a goose chase. 感谢所有回复的人,我很抱歉带着鹅追逐。 But in the end, the trouble of setting up a special debug station proved worth the effort. 但最终,设置一个特殊调试站的麻烦证明是值得的。

The fix: 修复:

Replacing the __declspec(thread) approach with calls to TLS methods. 用调用TLS方法替换__declspec(线程)方法。 At DllMain() I establish a TLS index and hold it globally, setting the initial value with TlsSetValue(). 在DllMain()中,我建立一个TLS索引并全局保存,使用TlsSetValue()设置初始值。 All subsequent requesters are then using that index to retrieve the TLS value with TlsGetValue(). 然后,所有后续请求者都使用该索引通过TlsGetValue()检索TLS值。 Whenever the value changes, just use TlsGetValue() again to set the value. 只要值发生变化,只需再次使用TlsGetValue()来设置值。 Always remember to cast with LPVOID as that is the type these use. 永远记得使用LPVOID进行强制转换,因为这是这些使用的类型。

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

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