Question

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

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

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

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

[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";
string s = Grouper.GrouperServer.GroupInit(this.strCommandFilePath, true, true);

But the DLL is returning the error: 'Cannot find command file: "C"' (the first character of the path only, which I have checked in the C++ DLL). For some reason the string this.strCommandFilePath is not being passed into the C++ method correctly.

What is wrong with the above call?


Edit to address comments.

The method being called in the if (sz = ::GroupInit((char*)bstrIniFile, 1)) statement is defined in a .c file and has the signature

char *GroupInit(char *szCmd, int iType)
{
   ...
}
Was it helpful?

Solution

It is a mistake to use TCHAR and related types here. The use case of TCHAR is for code that needs to compile for both Windows 9x which has no Unicode support, and Windows NT which does. Those days are long gone and TCHAR is obscuring the problem. What's more, the underlying code uses char* so it makes little sense to pretend that your wrapper code can do anything else. So switch to char.

On top of that you are casting away const. I guess because the function you call accepts a modifiable buffer for a parameter that it does not modify. Best solution is to fix the original library code that erroneously accepts char* and make it accept const char*. If you cannot do that then you'll need to cast away the const. But do that the C++ way with const_cast<>.

So, I'd have the C++ code like this:

BSTR GroupInit(const char* szIniFile, bool bDiagErr, bool bProcErr) {
   CString strResult = "";
   char* sz;
   ::SetVars(bDiagErr, bProcErr);
   if (sz = ::GroupInit(const_cast<char*>(szIniFile), 1))
      strResult = sz;
   return strResult.AllocSysString();
}

And the C# code should be:

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

Now, one wonders what happens to sz. Who is expected to deallocate that? Does it even need to be deallocated? Only you can answer those questions.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top