1

我定义了以下 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 大师,所以不要假设任何事情。这个定义的方式有什么明显的错误吗?是否有更多的封送处理(手动)?我觉得我可能遗漏了一些明显的东西。

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

4

2 回答 2

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

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

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

于 2013-04-19T11:16:05.930 回答
1

最后,这个问题与证据表明的完全不同(尤其是考虑到我对 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

关于延迟加载的部分适用于这种情况,因为由于 DllImport 正在加载有问题的 DLL。

感谢所有回复的人,我很抱歉导致了一场鹅追逐。但最终,设置一个专门的调试站的麻烦证明是值得的。

修复:

将 __declspec(thread) 方法替换为对 TLS 方法的调用。在 DllMain() 中,我建立了一个 TLS 索引并全局保存它,使用 TlsSetValue() 设置初始值。然后所有后续请求者都使用该索引通过 TlsGetValue() 检索 TLS 值。每当值更改时,只需再次使用 TlsGetValue() 来设置值。永远记住使用 LPVOID 进行投射,因为这是这些使用的类型。

于 2013-04-19T15:03:09.030 回答