1

I have a C++ dll that I am trying to get working in my C# project (I know nothing about C++/C but do have the source and can open and build it in VS2013)

The dll came with working examples in Excel VBA but I cant get it working in C#.

The error I keep coming up against is...

Additional information: A call to PInvoke function 'RMTest!RMTest.PInvokeTest::encode_eib_d' has unbalanced the stack. This is likely because the managed PInvoke signature does not match the unmanaged

My C# test code is as follows...

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Runtime.InteropServices;

namespace RMTest
{
    class Program
    {
        static void Main(string[] args)
        {
            string s1 = "2";
            string s2 = "1";
            string s3 = "1";
            string s4 = "1";
            string s5 = "TESTTE";

            var res = PInvokeTest.encode_eib_d(s1, s2, s3, s4, s5);
        }
    }

    class PInvokeTest
    {
        [DllImport("UKRM_EncodeEIB_D.dll", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Ansi)]
        public static extern string encode_eib_d(string infoID, string versionID, string format, string sClass, string licNum);
    }
}

For reference, the working example in Excel, the VBA is as follows...

Public Declare _
Function encode_eib_d Lib "UKRM_EncodeEIB_D.dll" (ByVal inInfoID As String, ByVal inVersionID As String, ByVal inClass As String, _
    ByVal inFormat As String, ByVal inLicenseNum As String) As Variant

Function ENCODE_D(inInfoID As String, inVersionID As String, inFormat As String, inClass As String, inLicenseNum As String) As Variant
    Dim error As Integer

    ChDrive (ActivePath())
    ChDir (ActivePath())

    On Error GoTo ErrHandler:
    ENCODE_D = encode_eib_d(inInfoID, inVersionID, inFormat, inClass, inLicenseNum)

ErrHandler:
    If Err.Number <> 0 Then
        ENCODE_D = Err.Description
    End If


End Function

The relevant snippet of code in the C++ dll (as far as I can tell) is as follows...

Header (.h):

#include "UKRM_EncodeEIB_D.h"
#include <WTypes.h>
#include <tchar.h>
#include <comutil.h>
#include <malloc.h>
#include <atlconv.h>

extern "C" {
 VARIANT __stdcall CallEncode(LPCSTR sInfoID, LPCSTR sVersionId, LPCSTR sFormat, LPCSTR sClass, LPCSTR sLicenseNum);
}

.cpp

#include "UKRM_EncodeEIB_D_DLL.h"


// ******************************************************************************
// * CallEncode
// ******************************************************************************
///
/// @brief Use of UKRM_Encode_EIB_D to provide DLL interface
///
/// @param [in]       sInfoID        barcode info ID
/// @param [in]       sVersionId     barcode version id
/// @param [in]       sFormat        barcode format
/// @param [in]       sClass         barcode class ID
/// @param [in]       sLicenseNum    barcode license number
///
/// @return @arg returns barcode if successful, otherwise returns error
///
/// @LM_Detailed_Description
/// Creates UKRM_Encode_EIB_D object, supplies input and returns result
//
// ******************************************************************************

VARIANT __stdcall CallEncode(LPCSTR sInfoID, LPCSTR sVersionId, LPCSTR sFormat, LPCSTR sClass, LPCSTR sLicenseNum)
{
  bool bRC;
  class UKRM_Encode_EIB_D stEncodeObject;
  char *srtnstring = NULL;

  bRC = stEncodeObject.encode((char *) sInfoID, (char *) sVersionId, (char *) sFormat, (char *) sClass, (char *) sLicenseNum);

  if(bRC == true)
  {
    stEncodeObject.get_BarcodeString(&srtnstring);
  }
  else
  {
    stEncodeObject.get_ErrorString(&srtnstring);
  }

  _bstr_t bstrt(srtnstring);

  return _variant_t(bstrt).Detach();
}

.def:

LIBRARY "UKRM_EncodeEIB_D"
EXPORTS
encode_eib_d = CallEncode
VERSION 1.0

I have tried various combinations of MarshalAs etc on the input parameters as well as trying StringBuilders instead of strings, but it sounds as if it isn't happy with the method signature from one to the other.

So frustrating that it works in VBA.

If you need anymore info please ask.

Thanks.

Dave S
  • 11
  • 1
  • Did you mean to switch the names of the 3/4 parameter of the PInvoke calls? Also the VB call returns varient, but in the C# call you return string. The closest match in C# is object. – Robert Snyder Jan 20 '15 at 13:26
  • It is because of the return type. Variant is a struct, you arbitrarily turned into a string for some reason. You need to change it to *object* and apply the [return:MarshalAs(UnmanagedType.Struct)] attribute. – Hans Passant Jan 20 '15 at 13:43
  • @Hans - I have used the below code posted by Jcl but changed the return type to the above object and return attribute... The error I am getting now is **Additional information: PInvoke restriction: cannot return variants.** – Dave S Jan 20 '15 at 14:12

1 Answers1

0

In your case, you'd need a pinvoke signature like:

[DllImport("UKRM_EncodeEIB_D.dll", CallingConvention = CallingConvention.StdCall)]
[return: MarshalAs(UnmanagedType.BStr)]
public static extern string encode_eib_d(
          [MarshalAs(UnmanagedType.LPStr)]string infoID, 
          [MarshalAs(UnmanagedType.LPStr)]string versionID, 
          [MarshalAs(UnmanagedType.LPStr)]string format, 
          [MarshalAs(UnmanagedType.LPStr)]string sClass, 
          [MarshalAs(UnmanagedType.LPStr)]string licNum);
Jcl
  • 27,696
  • 5
  • 61
  • 92