0
votes

I´m trying to call a method that is in a C++ dll declarated as __declspec(dllexport) to use in C#, but I don´t know how to return a string value from C++ and how to declare the signature using DllImport in C#.

C++ code "VNVAPI.dll"

  __declspec(dllexport) char * GetGpuName(int phyGPUid)
  {
      CNvidia * pInstance = CNvidia::GetInstance();
      char  szName[512]={0};
      pInstance->GetGpuName(phyGPUid,szName,512);
      return szName;
  }

C# method signature:

[DllImport("VNVAPI.dll")]
   public static extern  char GetGpuName(int phyGPUid);

Error generated:

A call to PInvoke function 'Core!Core.Hardware.IO.NVAPI::GetGpuName' has unbalanced the stack. This is likely because the managed PInvoke signature does not match the unmanaged target signature. Check that the calling convention and parameters of the PInvoke signature match the target unmanaged signature.

Thanks.

3
The C++ code is invalid, it returns a pointer to a buffer on the stack. Pretty classic bug. This function cannot be called safely from a C++ program either although it tends to work by accident. It can definitely not be pinvoked, the pinvoker marshaller will destroy the buffer content.Hans Passant
@Hans Passant: When you say C++, does that mean doing the same in C has different semantics?leppie
@Hans Passant: Drat, I got confused, again, was thinking you were mentioning the static scenario here. I still had @David Heffernan 's answer in my head. Is there any difference between the way memory is allocated for static between C and C++?leppie
@leppie, same thing. Using static isn't a fix, the pinvoke marshaller is going to try to release the string with CoTaskMemFree. That goes kaboom on Vista and Win7. The real fix is to pass the buffer + length as an arguments, StringBuilder on the managed side.Hans Passant
@Hans Passant: I do understand the proper and sane way. But why would the marshaller ever try to free memory it did not allocate? Surely it should just try copy the buffer pointed to (or make a string point to the memory location, if that is even possible).leppie

3 Answers

2
votes

As has been pointed out by others you need to specify the C calling convention in your P/Invoke and also use string on the managed side to marshal the null terminated char*.

However you should rejig the C++ routine to take a char* as an input parameter, together with a buffer length parameter. You then write into this buffer in the native code. This avoids the current problem that the data, as you presently have the code, is returned from the stack which, of course, is unwound as the function returns.

The suggestion to use static will make this memory global and so avoid stack unwind problems, at the expense of thread safety. Yes it will likely work for this use case but its a bad habit to get into.

0
votes

The error message might be confusing.

Without going into detail, try this:

static char  szName[512]={0};

If you still get the error, the you need to specify the calling convention in the DllImport attribute.

Edit:

Also make the return type string for C# method declaration.

0
votes

The error message suggests checking the calling convention. I suspect that the function is using C calling convention, which would explain the unbalanced stack. I would suggest that you specify the calling convention (as @leppi suggested) in your DllImport attribute. Like this:

[DllImport("VNVAPI.dll", CallingConvention=CallingConvention.Cdecl)]

According to the Strings samples, the return value should be string:

static extern string GetGpuName(int phyGPUid);