Search code examples
c#c++.netpinvokecalling-convention

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


I am having a weird problem: My Application was a very old VS project from 2009 / 2011. Today I converted it into an VS 2012 project and was able to compile and run fine. The App consist of an C# part and a C / C++ DLL that is being referenced like this:

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();
}

If I use the old project settings (with .Net Framework 2.0) the App runs fine. But then I wanted to include another C# Assembly, that required me to switch over to .Net Framework 4.0.

Now my code fails:

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

Visual Studio gives the following message:

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.

For the non german readers here, i would say, that C# replaces the correct signature of the method in .Net2.0 with a wrong one in .Net4.0

But why? And how can I correct this error?

Greetings Wolfgang

Addon:

Source code of the bms library:

// 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;
}

Solution

  • The default calling convention for your C++ DLL code is __cdecl.

    On the other hand, the default calling convention used by PInvoke is StdCall, i.e. __stdcall for native code (this is the calling convention used to call Win32 APIs).

    So, you have a calling convention mismatch: basically, the caller code and the called code disagree on things like how to pass parameters on the stack, who is responsible for popping arguments from the stack, etc., causing a stack imbalance error, that is correctly detected in your case.

    If you don't want to modify your native C++ DLL, you should specify the calling convention explicitly in your PInvoke declarations, adding a CallingConvention=CallingConvention.Cdecl specification, e.g.:

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