使用 .Net 4.0 时出现 PInvoke StackImbalance 错误,但使用 .Net 2.0 时则不会

PInvoke StackImbalance error when using .Net 4.0 but not when using .Net 2.0

我有一个奇怪的问题: 我的应用程序是 2009 / 2011 年的一个非常古老的 VS 项目。今天我将它转换为 VS 2012 项目并且能够编译并且 运行 很好。 该应用程序由一个 C# 部分和一个 C/C++ DLL 组成,被引用如下:

class clsBMS_KommInternal
{
    // initialization and deinitialization
    [DllImport("bms.dll", EntryPoint = "InitBMS_SM", SetLastError = true, CharSet = CharSet.Auto)]
    internal unsafe static extern bool _InitBMS_SM(UInt32* p_dwGroesse, ushort** pp_ptrBase, UInt32* p_dwError);

    [DllImport("bms.dll", EntryPoint = "DeInitBMS_SM", SetLastError = true, CharSet = CharSet.Auto)]
    internal unsafe static extern bool _DeInitBMS_SM(UInt32* p_dwError);

    [DllImport("bms.dll", EntryPoint = "InitHeaderSM", SetLastError = true, CharSet = CharSet.Auto)]
    // internal unsafe static extern bool _InitHeaderSM(UInt32* p_dwError);
    internal unsafe static extern bool _InitHeaderSM(UInt32* p_dwError);

    [DllImport("bms.dll", EntryPoint = "GetBasePointerBMS_SM", SetLastError = true, CharSet = CharSet.Auto)]
    internal unsafe static extern Byte* _GetBasePointerBMS_SM();

    [DllImport("bms.dll", EntryPoint = "GetBasePointerBMS_SMHeader", SetLastError = true, CharSet = CharSet.Auto)]
    internal unsafe static extern Byte* _GetBasePointerBMS_SMHeader();

    // Claim and release Mutex to GDB
    [DllImport("bms.dll", EntryPoint = "ClaimMutexBMS_SM", SetLastError = true, CharSet = CharSet.Auto)]
    internal static extern bool _ClaimMutexBMS_SM(UInt32 dwTimeout);

    [DllImport("bms.dll", EntryPoint = "ReleaseMutexBMS_SM", SetLastError = true, CharSet = CharSet.Auto)]
    internal static extern bool _ReleaseMutexBMS_SM();
}

如果我使用旧项目设置(使用 .Net Framework 2.0),应用程序 运行 没问题。 但后来我想包含另一个 C# 程序集,这需要我切换到 .Net Framework 4.0。

现在我的代码失败了:

 UInt32 dwGroesse;
 UInt32 dwFehler;
 int iFehler;
 ushort* pBase;
 // Zuerst die allgemeine Initialisierung
 clsBMS_KommInternal._InitHeaderSM(&dwFehler);

Visual Studio 给出以下消息:

PInvokeStackImbalance occurred
Message: Managed Debugging Assistant 'PInvokeStackImbalance' has detected a problem in 'D:\Wolfgang\Documents\Visual Studio 2015\Projects\BPara_CS\WindowsFormsApplication1\bin\x86\Debug\WindowsFormsApplication1.vshost.exe'.
Additional information: Ein Aufruf an die PInvoke-Funktion "WindowsFormsApplication1!FlexCommInternal.BMS_KommDLLImport.clsBMS_KommInternal::_InitHeaderSM" hat das Gleichgewicht des Stapels gestört. Wahrscheinlich stimmt die verwaltete PInvoke-Signatur nicht mit der nicht verwalteten Zielsignatur überein. Überprüfen Sie, ob die Aufrufkonvention und die Parameter der PInvoke-Signatur mit der nicht verwalteten Zielsignatur übereinstimmen.

对于这里的非德国读者,我想说的是,C# 将 .Net2.0 中方法的正确签名替换为 .Net4.0 中的错误签名

但是为什么呢? 我该如何纠正这个错误?

您好 沃尔夫冈

插件:

bms库源代码:

// bms.cpp : Definiert den Einstiegspunkt für die DLL-Anwendung.
//

#include "stdafx.h"
#include <stdio.h>
#include <winbase.h>
#include "include/bms.h"
//*****************************************************************************
//
// Defines
//
//*****************************************************************************
// Name des SharedMemory fuer die Bundmutter-Station
#define SHMEM_BPARA_ID "B-PARA-SM"

// Name des SharedMemory fuer die Header-Struktur
#define SHMEM_BPARA_HEADER_ID "B-PARA-SM_HEADER"

// Name des Mutex fuer den exclusiven Zugriff
#define SHMEM_BPARA_MUTEX "MUTEX_BPARA_SM"



#define TIMEOUT_MUTEX_BPARA_SM INFINITE 


//*****************************************************************************
//
// globale Variable
//
//*****************************************************************************
HANDLE hMutexSM    = 0 ; 

HANDLE hSMHeader    = 0 ;
HANDLE hSMBPARA       = 0 ;

// Zeiger auf das SharedMemory fuer die Header-Struktur
// ToDo: Typ definieren
psBMSDLLHEADER pSMBParaHeader = 0 ;
LPVOID pSharedMemBPara;   // Zeiger auf den Shared-Memory-Bereich

// internal functions
static BOOL CheckOffset(DWORD dwOffsetHighByte);
static BOOL CreateMutexBMS(DWORD* p_dwError);
static BOOL OpenMutexBMS(DWORD* p_dwError);






BOOL APIENTRY DllMain( HANDLE hModule, 
                      DWORD  ul_reason_for_call, 
                      LPVOID lpReserved
                      )
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}

HANDLE OpenSharedMemory(DWORD DesiredAccess,BOOL bInheritHandle,LPCTSTR lpName,VOID ** location)
{
    HANDLE hMapFile;
    LPVOID pBuf;

    hMapFile = OpenFileMapping(
        FILE_MAP_ALL_ACCESS,   // read/write access
        bInheritHandle,                 // do not inherit the name
        lpName);               // name of mapping object 

    if (hMapFile == NULL || hMapFile == INVALID_HANDLE_VALUE) 
    { 
        return NULL;
    }
    pBuf = (LPVOID) MapViewOfFile(hMapFile,   // handle to map object
        FILE_MAP_ALL_ACCESS, // read/write permission
        0,                   
        0,                   
        0);           

    if (pBuf == NULL) 
    { 
        return NULL;
    }
    *location = pBuf;
    return hMapFile;
} 

HANDLE CreateSharedMemory(DWORD flProtect,DWORD MaximumSizeHigh,DWORD MaximumSizeLow,LPCTSTR lpName,VOID ** location)
{
    HANDLE hMapFile;
    VOID *pBuf;

    hMapFile = CreateFileMapping(INVALID_HANDLE_VALUE, NULL,flProtect, MaximumSizeHigh,MaximumSizeLow,lpName);

    if (hMapFile == NULL || hMapFile == INVALID_HANDLE_VALUE) 
    { 
        return NULL;
    }
    pBuf = (LPVOID) MapViewOfFile(hMapFile,   // handle to map object
        FILE_MAP_ALL_ACCESS, // read/write permission
        0,
        0,
        0);

    if (pBuf == NULL) 
    { 
        return NULL;
    }
    *location = pBuf;
    return hMapFile;

}



//****************************************************************************
//
//  static BOOL InitHeaderSM()
//  
//  SharedMemory fuer Header anlegen bzw. laden
//  
//  Parameter:
//      keiner
//
//  Return:
//      TRUE, wenn alles ok,
//      FALSE, bei Fehler
//
//****************************************************************************
BOOL InitHeaderSM(DWORD* p_dwError)
{
    LPVOID  pHeader;


    * p_dwError = kBMSSuccess;

    // Gibt es bereits ein SharedMemory fuer den Header , so bekomme ich hier das Handle zurueck
    hSMHeader = OpenSharedMemory(PAGE_READWRITE, FALSE, SHMEM_BPARA_HEADER_ID, (void **)&pHeader);

    // Es gab kein SharedMenory
    if(NULL == hSMHeader)
    {
        // SharedMenory explizit anlegen und initialisieren
        hSMHeader= CreateSharedMemory(PAGE_READWRITE, 0, sizeof(sBMSDLLHEADER), SHMEM_BPARA_HEADER_ID, (void **)&pHeader);

        // wenn SharedMenory vorhanden, initialisieren
        if(NULL != hSMHeader && NULL != pHeader)
        {
            //            ClaimMutexBMS(INFINITE);
            {
                memset(pHeader,0x00,sizeof(sBMSDLLHEADER));
                pSMBParaHeader = (psBMSDLLHEADER) pHeader;
                pSMBParaHeader->dwVersion   = SHMEM_BMS_SM_VERSION + sizeof(sBMSDLLHEADER);  // momentan gueltige Version der BMS
                pSMBParaHeader->dwClients ++;

            }
            //            ReleaseMutexBMS();
        }
        else
        {
            // error creating shared memory
            pHeader = NULL;
            pSMBParaHeader = (psBMSDLLHEADER)pHeader;

            if(NULL != p_dwError)
            {
                *p_dwError = kBMSErrorNotCreated;
            }
            return FALSE;

        }
    }
    else
    {
        // es gibt bereits ein SharedMemory fuer den Header
        // Zeiger auf den Header des SharedMenory wurde bereits durch
        // OpenSharedMemory gesetzt

        pSMBParaHeader = (psBMSDLLHEADER)pHeader;
        //        ClaimMutexBMS(INFINITE);
        {
            pSMBParaHeader->dwClients ++;
        }
        //        ReleaseMutexBMS();

        return TRUE;

    }
    pSMBParaHeader = (psBMSDLLHEADER)pHeader;
    return TRUE;


}
//****************************************************************************
//
//  static BOOL DeInitHeaderSM()
//  
//  SharedMemory entladen, bzw. loeschen
//  
//  Parameter:
//      keiner
//
//  Return:
//      TRUE, wenn alles ok,
//      FALSE, bei Fehler
//
//****************************************************************************
BOOL DeInitHeaderSM(DWORD* p_dwError)
{
    BOOL bErg;
    bErg  =TRUE;
    *p_dwError = kBMSSuccess;
    //    ClaimMutexBMS(INFINITE);
    {
        pSMBParaHeader->dwClients --;

        if(pSMBParaHeader->dwClients == 0)
        {
            if (hSMBPARA != 0)
            {
                CloseHandle(hSMBPARA);
                hSMBPARA = 0;
                pSMBParaHeader = NULL;
            }

            CloseHandle(hSMHeader);
            hSMHeader = 0;
            pSMBParaHeader = NULL;
        }
    }
    //    ReleaseMutexBMS();


    return bErg;

}

//****************************************************************************
//
//  Die Funktion erzeugt den shared memory-Bereich für die BMS und 
//  initialisiert mit NULL.
//  
//    Parameter:
//    p_dwSize - Pointer zur Größenangabe für die BMS 
//    pp_bBase - Pointer zur Rückgabe des Base Pointers
//    p_dwError - Pointer zur Rückgabe des Error-Codes
//    
//    Return:
//    BOOL - TRUE bei erfolgreichem Öffnen bzw. Erzeugung und Initialisierung, 
//           FALSE bei einem Fehler. siehe ErrorCode in p_dwError.
//      
//****************************************************************************
BOOL InitBMS_SM(DWORD* p_dwSize, BYTE** pp_bBase, DWORD* p_dwError)
{
    BOOL bErg = FALSE;
    if(NULL != p_dwError)
        *p_dwError = kBMSSuccess;

    // Ueberpruefen auf Parameter
    if(NULL == p_dwSize || NULL == pp_bBase)
    {
        if(NULL != p_dwError)
            *p_dwError = kBMSErrorInvalidParam;
        return bErg;
    }

    //    ClaimMutexBMS(INFINITE);

    // Gibt es bereits ein SharedMemory, so bekomme ich hier das Handle zurueck
    hSMBPARA= OpenSharedMemory(PAGE_READWRITE, FALSE, SHMEM_BPARA_ID, &pSharedMemBPara);

    // Es gab kein SharedMenory
    if(NULL == hSMBPARA)
    {
        // SharedMenory explizit anlegen und initialisieren
        hSMBPARA= CreateSharedMemory(PAGE_READWRITE, 0, *p_dwSize, SHMEM_BPARA_ID, &pSharedMemBPara);

        // wenn SharedMenory vorhanden, initialisieren
        if(NULL != hSMBPARA && NULL != pSharedMemBPara)
        {
            // Zeiger auf den Header des SharedMenory Durch Aufruf abgedeckt 


            // Adresse der BMS zurueck geben 
            *pp_bBase = (BYTE*)pSharedMemBPara;

            // Header-Infos beschreiben
            pSMBParaHeader->dwClients       = 1;                  // ich habe es angelegt, also 1
            pSMBParaHeader->dwUsers         = 1;                  // InitBMS wird gerade ausgefuehrt
            pSMBParaHeader->dwGroesse       = *p_dwSize;          // Groesse, so wie es der User moechte

            // initialize BMS (beginning after header of sharedmem)
            memset((BYTE*)pSharedMemBPara,0,*p_dwSize);
            bErg =TRUE;
        }
        else
        {
            // error creating shared memory
            *pp_bBase = NULL;

            if(NULL != p_dwError)
            {
                *p_dwError = kBMSErrorNotCreated;
            }
            bErg = FALSE;
        }
    }
    else
    {
        // es gibt bereits ein BMS

        // Zeiger auf den Header des SharedMenory wurde bereits durch
        // OpenSharedMemory gesetzt

        // Versionskontrolle
        if (pSMBParaHeader->dwVersion != SHMEM_BMS_SM_VERSION + sizeof(sBMSDLLHEADER))
        {
            if(NULL != p_dwError)
                *p_dwError = kBMSErrorInvalidDLLVersion;
            bErg = FALSE;
        }
        else

        {
            // aktuelle Groesse des BMS-Speichers zurueckgeben

            *p_dwSize = pSMBParaHeader->dwGroesse;
            // Anzahl Benutzer um eins erhoehen
            pSMBParaHeader->dwUsers++;

            *pp_bBase = (BYTE*)pSharedMemBPara;

            if(NULL != p_dwError)
                *p_dwError = kBMSErrorInitAlreadyDone;
            bErg = TRUE;
        }
    }
    //    ReleaseMutexBMS();
    return bErg;
}

//****************************************************************************
//
//  BOOL DeInitBMS(DWORD* p_dwError)
//  
//  Das BMS-Sharedmemory freigeben
//  
//  Parameter:
//    p_dwError - Pointer zur Rückgabe des Error-Codes
//    
//    Return:
//    BOOL - TRUE bei erfolgreichem Öffnen bzw. Erzeugung und Initialisierung, 
//           FALSE bei einem Fehler. siehe ErrorCode in p_dwError.
//****************************************************************************
BOOL DeInitBMS_SM(DWORD* p_dwError)
{
    BOOL bErg;
    * p_dwError = kBMSSuccess;
    //    ClaimMutexBMS(INFINITE);

    pSMBParaHeader->dwUsers--;
    if (pSMBParaHeader->dwUsers == 0 )
    {
        CloseHandle(hSMBPARA);
        hSMBPARA = NULL;
        pSMBParaHeader = NULL;
    }

    bErg = TRUE;
    //    ReleaseMutexBMS();
    return bErg;

}

//****************************************************************************
//
//  BYTE* GetBasePointerBMS()
//  
//  Den Zeiger auf den Anfang des SharedMemory zurueckgeben
//  
//  Parameter:
//      keiner
//
//  Return:
//      Byte-Zeiger
//
//
//****************************************************************************
BYTE* GetBasePointerBMS_SM()
{
    return (BYTE*) pSharedMemBPara;
}

//****************************************************************************
//
//  BYTE* GetBasePointerBMSHeader()
//  
//  Den Zeiger auf den Anfang des SharedMemory zurueckgeben
//  
//  Parameter:
//      keiner
//
//  Return:
//      Byte-Zeiger
//
//
//****************************************************************************
BYTE* GetBasePointerBMS_SMHeader()
{
    return (BYTE*) pSMBParaHeader;
}


// Read or write Bytes, Words, DWords or many Bytes from BMS_SM
BYTE ReadByteBMS_SM                      (DWORD dwOffset, DWORD* dwError)
{
return 0;
}
WORD ReadWordBMS_SM                      (DWORD dwOffset, DWORD* dwError)
{
return 0;
}
DWORD ReadDwordBMS_SM                    (DWORD dwOffset, DWORD* dwError)
{
return 0;
}
BOOL ReadManyBMS_SM                      (DWORD dwOffset, BYTE* p_bTarget, DWORD dwLength, DWORD* p_dwError)
{
return 0;
}
BOOL ReadManyBMS_SMDownward             (DWORD dwOffset, BYTE* p_bTarget, DWORD dwLength, DWORD* p_dwError)
{
return 0;
}
BOOL WriteByteBMS_SM                     (DWORD dwOffset, BYTE bWert, DWORD* dwError)
{
return 0;
}
BOOL WriteWordBMS_SM                     (DWORD dwOffset, WORD wWert, DWORD* dwError)
{
return 0;
}
BOOL WriteDwordBMS_SM                    (DWORD dwOffset, DWORD dwWert, DWORD* dwError)
{
return 0;
}
BOOL WriteManyBMS_SM                     (DWORD dwOffset, BYTE* p_bSource, DWORD dwLength, DWORD* p_dwError)
{
return 0;
}
BOOL WriteManyBMS_SMDownward            (DWORD dwOffset, BYTE* p_bSource, DWORD dwLength, DWORD* p_dwError)
{
return 0;
}


//****************************************************************************
//
//  Die Funktion erzeugt ein Mutex für den Zugriff auf das shared memory GDB.
//  
//    Parameter:
//    p_dwError - Pointer zur Rückgabe des Error-Codes
//    
//      Return:
//      TRUE - Mutex erfolgreich erzeugt
//      FALSE - Fehler beim Erzeugen
//      
//****************************************************************************
BOOL CreateMutexBPARA_SM(DWORD* p_dwError)
{
    // requests MUTEX_ALL_ACCESS access to the existing mutex object
    hMutexSM = CreateMutex(NULL, FALSE, SHMEM_BPARA_MUTEX);

    if(NULL == hMutexSM)
    {
        if(NULL != p_dwError)
            *p_dwError = kBMSErrorMutexNotCreated;
        return FALSE;
    }
    return TRUE;
}

//****************************************************************************
//
//  Die Funktion öffnet das Mutex für den Zugriff auf das shared memory GDB.
//  Das Mutex muss zuvor mit CreateMutexGDB erzeugt werden. Andernfalls wird 
//  FALSE zurückgegeben.
//  
//    Parameter:
//    p_dwError - Pointer zur Rückgabe des Error-Codes
//    
//      Return:
//      TRUE - Mutex erfolgreich geöffnet
//      FALSE - Mutex nicht geöffnet
//      
//****************************************************************************
BOOL OpenMutexBPARA_SM(DWORD* p_dwError)
{
    // The first two parameters are ignored (see RTX 6.0 documentation)
    hMutexSM = OpenMutex(NULL, FALSE, SHMEM_BPARA_MUTEX);

    if(NULL == hMutexSM)
    {
        if(NULL != p_dwError)
            *p_dwError = kBMSErrorMutexNotOpened;
        return FALSE;
    }
    return TRUE;

}

//****************************************************************************
//
//  Die Funktion wartet auf die Freigabe des Mutex für den Zugriff auf die GDB. 
//  
//    Parameter:
//    dwTimeout - Maximale Wartezeit in ms. Nach Verstreichen der Zeit wird
//                die Mutexanforderung abgebrochen.
//    
//      Return:
//      TRUE - Mutex erfolgreich angefordert
//      FALSE - Mutex nicht angefordert
//      
//****************************************************************************
BOOL ClaimMutexBMS_SM(DWORD dwTimeout)
{
    DWORD dwWaitResult;
    DWORD dwError;

    if(NULL == hMutexSM)
    {
        if(!OpenMutexBPARA_SM(&dwError))
            CreateMutexBPARA_SM(&dwError);
    }

    dwWaitResult = WaitForSingleObject(hMutexSM, dwTimeout);
    switch (dwWaitResult) 
    {
        // The thread got mutex ownership.
    case WAIT_OBJECT_0: 
        return TRUE;

        // Cannot get mutex ownership due to time-out.
    case WAIT_TIMEOUT: 
        return FALSE; 

        // Got ownership of the abandoned mutex object.
    case WAIT_ABANDONED: 
        return FALSE; 
    }

    return FALSE;
}
//****************************************************************************
//
//  Die Funktion gibt das Mutex für den Zugriff auf die GDB wieder frei.
//  
//    Parameter:
//      keine
//    
//      Return:
//      TRUE - Mutex freigegeben
//      FALSE - Mutex nicht freigegeben
//      
//****************************************************************************
BOOL ReleaseMutexBMS_SM()
{
    if(NULL != hMutexSM)
    {
        return ReleaseMutex(hMutexSM);
    }
    else
        return FALSE;
}

C++ DLL 代码的默认调用约定是 __cdecl

另一方面,PInvoke is StdCall, i.e. __stdcall 用于本机代码的默认调用约定(这是用于调用 Win32 API 的调用约定)。

所以,调用约定不匹配:基本上,调用者代码和被调用代码在如何在堆栈上传递参数、谁负责从堆栈中弹出参数等问题上存在分歧,从而导致堆栈不平衡错误,在您的情况下已正确检测到。

如果您不想修改您的本机 C++ DLL,您应该在您的 PInvoke 声明中显式指定调用约定,添加 CallingConvention=CallingConvention.Cdecl 规范,例如:

[DllImport("bms.dll", ..., CallingConvention=CallingConvention.Cdecl)
...