Search code examples
c#c++dllpinvokedllimport

How to Correctly Call C++ DLL from C# using Correct Parameter Types


I have been provided with a DLL which is to be called by C# et al. The DLL contains two methods as follows

extern "C" {
   __declspec(dllexport) BSTR GroupInit(LPCTSTR bstrIniFile, bool bDiagErr, bool bProcErr);
   __declspec(dllexport) void G();
} 

class GrouperServer {
public:
   BSTR GroupInit(LPCTSTR bstrIniFile, bool bDiagErr, bool bProcErr);
   void G();
}

BSTR GrouperServer::GroupInit(LPCTSTR bstrIniFile, bool bDiagErr, bool bProcErr) {
   CString strResult = "";
   char* sz;
   SetVars(bDiagErr, bProcErr);

   if (sz = ::GroupInit((char*)bstrIniFile, 1))
      strResult = sz;
   return strResult.AllocSysString();
}

void G() {
   MessageBox(0, "And text here", "MessageBox caption", MB_OK);
}

I am attempting to call these DLLs from C# by first defining the class:

public class GrouperServer {
    [DllImport("GrouperServer.dll", CallingConvention = CallingConvention.Cdecl)]
    public static extern void G();

    [DllImport("GrouperServer.dll", CallingConvention = CallingConvention.Cdecl)]
    [return: MarshalAs(UnmanagedType.BStr)]
    public static extern string GroupInit(
        string strCmdFile, bool bAllowBadDiagCodes, bool bAllowBadProcCodes);
}

and doing

this.strCommandFilePath = "C:\\MyDir\\MyCommandFile.txt";
Grouper.GrouperServer.G();
Grouper.GrouperServer.GroupInit(this.strCommandFilePath, true, true);

The call to method G() works and I get a message box, but for the call to GroupInit(), I get

An unhandled exception of type 'System.EntryPointNotFoundException' occurred in DrGroupIN.exe. Additional information: Unable to find an entry point named 'GroupInit' in DLL 'GrouperServer.dll'.

How can I call the second method GrouInit(...) with the correct parameters in this case?


Edit 1.

I have also tried

[DllImport("GrouperServer.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr GroupInit(
    string strCmdFile, bool bAllowBadDiagCodes, bool bAllowBadProcCodes);

where this is called via:

IntPtr ptr = Grouper.GrouperServer.GroupInit(this.strCommandFilePath, true, true);
string strString = Marshal.PtrToStringBSTR(ptr);
Marshal.FreeBSTR(ptr);

But this too throws the error above.

Edit 2.

I have also tried

[DllImport("GrouperServer.dll", CallingConvention = CallingConvention.Cdecl, CharSet=CharSet.Unicode)]
[return: MarshalAs(UnmanagedType.BStr)]
public static extern string GroupInit(
    [MarshalAs(UnmanagedType.LPTStr)]string strCmdFile, 
    bool bAllowBadDiagCodes, 
    bool bAllowBadProcCodes);

But this too throws the error above.


Solution

  • It looks to me as though you cannot call this DLL using C#. The DLL exports member functions of a class. And you cannot instantiate that class.

    I can see the following options:

    1. Ask the DLL vendor to export either static member functions, or non-member functions. These should then be accessible using p/invoke.
    2. Write a mixed mode C++/CLI wrapper around the DLL. This wrapper can readily consume the unmanaged DLL. In turn it can then expose a managed ref class that wraps the functionality. The C++/CLI wrapper can then be added as a reference to the C# project.

    That said, these declarations seem to be in conflict:

    extern "C" {
       __declspec(dllexport) BSTR GroupInit(LPCTSTR bstrIniFile, bool bDiagErr, bool bProcErr);
       __declspec(dllexport) void G();
    } 
    
    class GrouperServer {
    public:
       BSTR GroupInit(LPCTSTR bstrIniFile, bool bDiagErr, bool bProcErr);
       void G();
    }
    

    The first pair of declarations seem to be non-member functions. But they are followed by a class with member functions having the same names. You do need to be clear which functions you are attempting to call.

    Perhaps the DLL already contains non-member functions that wrap up the member functions. In which case you just need to find out the names by which they are exported. Use Dependency Walker to do that.

    So, how to declare the p/invoke. You need to know the character set in use, and the name by which the function is exported. Let's assume Unicode. The p/invoke would be:

    [DllImport("GrouperServer.dll", CallingConvention = CallingConvention.Cdecl,
        EntryPoint = "<exported name goes here>")]
    [return: MarshalAs(UnmanagedType.BStr)]
    public static extern string GroupInit(
        [MarshalAs(UnmanagedType.LPWStr)]
        string strCmdFile, 
        bool bAllowBadDiagCodes, 
        bool bAllowBadProcCodes
    );