[英]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.