2012-11-29 98 views
9

我想我已經基本理解了如何編寫回調函數的c#委託,但是這個讓我很困惑。 C++的定義如下:用於C++回調的C#委託

typedef int (__stdcall* Callback)(
long lCode, 
long lParamSize, 
void* pParam 
); 

和我的C#的做法是:

unsafe delegate int CallbackDelegate (int lCode, int lParamSize, IntPtr pParam); 

雖然這似乎是不正確的,因爲我得到一個PInvokeStackInbalance錯誤,這意味着我委託的定義是錯的。

該函數的其餘參數是字符串或整數,這意味着它們不會導致錯誤,如果我只是傳遞一個IntPtr.Zero而不是委託(這意味着我指向一個非空字符串)現有的回調函數)我得到一個AccessViolation錯誤,這也是有道理的。

我在做什麼錯?

編輯:

完整的C++函數是:

int 
__stdcall 
_Initialize (
const char*   FileName, 
Callback   cbFunction, 
int     Code, 
const char*   Name, 
unsigned int  Option, 
unsigned int  Option2 
); 

我的C#的版本是:

[DllImport("MyDll.dll", CallingConvention = CallingConvention.StdCall)] 
public static extern int _Initialize (string FileName, CallbackDelegate cbFunction, int Code, string Name, uint Options, uint Options2); 

的功能(用於測試)剛打電話的主程序裏面控制檯應用程序:

static void Main(string[] args) 
{ 
    CallbackDelegate del = new CallbackDelegate(onCallback); 

    Console.Write(_Initialize("SomeFile.dat", del, 1000, "", 0, 4)); 
    Console.Read(); 
} 

其中onCallback是這樣的:

static int onCallback(int lCode, int lParamSize, IntPtr pParam) 
{ 
    return 0; 
} 

我得到了PInvokeStackInbalance錯誤在哪裏我叫_Initialize行,如果我通過IntPtr.Zero而不是委託,函數的定義修改爲IntPtr而不是CallbackDelegate然後我收到一個AccessViolationException

+0

'函數的參數的其餘部分是字符串或整數「。不要隱藏信息。 –

+1

你有沒有試過不使用不安全?即:代表int CallbackDelegate(int lCode,int lParamSize,IntPtr pParam); – DougEC

+0

@HansPassant我不是在試圖隱藏信息,我試圖離開我認爲不相關的東西。我將編輯問題並添加信息。 – Valandur

回答

3

NET long是64位。 C++ long可能只是32位。請與編譯該定義的C++編譯器一起檢查它的大小爲long

+0

良好的輸入,可悲的是,這似乎並沒有解決問題。我無法檢查C++編譯器,因爲我只有.dll和.h文件,但將long更改爲Int32並沒有幫助。 – Valandur

3
[UnmanagedFunctionPointerAttribute(CallingConvention.StdCall)] 
unsafe delegate int CallbackDelegate (int lCode, int lParamSize, IntPtr pParam); 

如果.NET假定cdecl而不是stdcall,那麼你的堆棧肯定會被洗掉。

4

我將你的代碼添加到我當前的項目中,我在VS2012中做了很多C#/ C++互操作。雖然我討厭使用「它在我的機器上工作」,但它對我來說工作得很好。我在下面列出的代碼僅僅是爲了說明我沒有做出根本性的改變。

我的建議是,你可以使用_Initialize的存根函數構建一個新的本地dll,例如下面的代碼,並且看看它是否可以在你控制接口的兩側時工作。如果可行,但真正的DLL不會,它歸結爲本地端的編譯器設置,或者它們是本地端的錯誤,並且它正在堆棧中跺腳。

extern "C" { 

    typedef int (__stdcall* Callback)(long lCode,long lParamSize,void* pParam); 

    TRADITIONALDLL_API int __stdcall _Initialize (const char* FileName,Callback cbFunction, int     Code, 
           const char*   Name,unsigned int  Option,unsigned int  Option2) 
    { 
     cbFunction(0, 0, nullptr); 
     return 0; 
    } 
} 

在C#的一面,我加入了聲明,以我的接口類:

public delegate int CallbackDelegate(int lCode, int lParamSize, IntPtr pParam); 

[DllImport("XXX.dll", CallingConvention = CallingConvention.StdCall)] 
public static extern int _Initialize(string FileName, CallbackDelegate cbFunction, int Code, string Name, uint Options, uint Options2); 

然後在我的主:

private int onCallback(int lCode, int lParamSize, IntPtr pParam) 
{ 
     return 0; 
} 
XXXInterface.CallbackDelegate del = new XXXInterface.CallbackDelegate(onCallback); 

Console.Write(XXXInterface._Initialize("SomeFile.dat", del, 1000, "", 0, 4)); 
+0

謝謝你檢查了這一點,應該自己做。我也試過了,它似乎工作。 還發現的是,應用程序似乎工作,如果我只是啓動exe文件,沒有調試... – Valandur

+0

這很奇怪。這聽起來像是在C++邊的dll中存在一個影響堆棧的錯誤。有沒有什麼辦法讓你獲得本地dll的源代碼,以便你可以在調試器下運行它們?我不知道有任何其他的方法來處理它,幾乎可以肯定沒有辦法將它與源碼固定在一起。 –

+0

不,可惜我無法獲得源代碼,否則我認爲這會容易得多。我試着從C++程序中調用C++函數,這似乎工作得很好。因此,我認爲DLL是好的,我只是在回調函數中犯了一個錯誤。感謝您的幫助。 – Valandur