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