tags:

views:

111

answers:

2

I have written a C++ wrapper DLL for C# to call. The DLL was tested and worked fine with my C++ test program.

now integrated with C#, I got runtime error and crashed. Cannot use debugger to see more details.

The C++ side has only one method:

#ifdef DLLWRAPPERWIN32_EXPORTS
#define DLLWRAPPERWIN32_API __declspec(dllexport)
#else
#define DLLWRAPPERWIN32_API __declspec(dllimport)
#endif

#include "NB_DPSM.h"

extern "C" {
 DLLWRAPPERWIN32_API int WriteGenbenchDataWrapper(string fileNameToAnalyze, 
  string parameterFileName,  
  string baseNameToSaveData,
  string logFileName,
  string& message) ;
}

in the C# side, there is a definition,

[DllImport("..\\..\\thirdParty\\cogs\\DLLWrapperWin32.dll")]
public static extern int WriteGenbenchDataWrapper(string fileNameToAnalyze, 
              string parameterFileName,  
              string baseNameToSaveData,
              string logFileName, 
              ref string message);

and a call:

 string msg = "";
    int returnVal = WriteGenbenchDataWrapper(rawDataFileName, 
                   parameterFileName, outputBaseName, logFileName, ref msg);

I guess there must be something wrong with the last parameter of the function. string& in C++ should be ref string in C#?

EDIT:

Do we really need the extern "C"?

EDIT 2:

after I remove the extern "C from the dll, I got the EntryPointNotFoundException. When I look at the dll by using DLL Export Viewer, I found the function name is "int __cdecl WriteGenbenchDataWrapper(class std:: ..." Do I need to include the " __cdecl"?

+3  A: 

There are a bunch of rules for marsheling with PInvoke. For reference Marsheling between managaed & unmanaged

Focusing on the C# side first. If you knew a reasonable size of the message up front you could use StringBuilder type and define that size, something like.

[DllImport("DLLWrapperWin32.dll")]
public static extern int WriteGenbenchDataWrapper(string fileNameToAnalyze, 
                                                    string parameterFileName,   
                                                    string baseNameToSaveData,
                                                    string logFileName, 
                                                    StringBuilder message
                                                    int messageLength );

Impression from the name message (and other posts) indiciates you don't know the size up front, and you won't be passing a partial message to the function so maybe

[DllImport("DLLWrapperWin32.dll")]
public static extern int WriteGenbenchDataWrapper(in string fileNameToAnalyze, 
                                                    in string parameterFileName,   
                                                    in string baseNameToSaveData,
                                                    in string logFileName, 
                                                    out string message );

Now on the C/C++ side - to match the second definition

extern "C" // if this is a C++ file to turn off name mangling for this function only
int WriteGenbenchDataWrapper( char * fileNameToAnalyze, 
                              char * parameterFileName,   
                              char * baseNameToSaveData,
                              char * logFileName, 
                              char ** message ) {
  string internalMessage;
  SomeFunc( internalMessage ); // these functions won't have extern "C" applied
  * message = (char *)::CoTaskMemAlloc(internalMessage.length()+1); 
  strcpy(* message, internalMessage.c_str());
}

Consideration of unicode/ansi strings is also important, refer to [MarshalAsAttribute(UnmanagedType.LPWSTR)]

For release mode you will want to remove your development path settings "..\..\thirdParty\cogs"

Greg Domjan
This is a wrapper, i have to call a dll from thirdparty and that dll is a C++ dll with class member functions. I was told I cannot use "extern "C"" in my code. Is that right?
5YrsLaterDBA
You can use extern "C" in your code for your interface, just don't apply it to the C++ interface.
Greg Domjan
@Greg how can I delete the space allocated by CoTaskMemAlloc() ?
5YrsLaterDBA
Have another good read of the link. Basically .net is using CoTaskMemAlloc/CoTaskMemFree internally. If you need to return a new string to .net your unmanaged part allocates it and then .net can free it.
Greg Domjan
+1  A: 

In your C++ code:

I've always needed the extern "C". C++ mangles function names if you don't (the mangling is needed to support function overloading). The extern "C" tells it not to do this.

I also will declare the functions as __stdcall. I believe you can tell C# which type of calling convention to use, but I think __stdcall is the default.

As far as passing a string object, I'm not sure about that, I stick to only using primitives for parameter passing, so I would use const char * and adjust accordingly in my C++ code.

Also, I try to avoid passing by reference. Rather, if I need to return several values, I'll set up a series of getters to handle this (a const char * returns as an IntPtr).

In your C# code:

I use String for the const char *, int for int, and so on. I believe Microsoft has a chart somewhere to tell you what should sub in for what.

When dealing with a returned string, you need to convert it to ANSI. This can be done with a call to Marshal.PtrToStringAnsi().

For Example:

In my C++ code:

extern "C" __declspec(dllexport) const char* __stdcall GetCompany(const char *In) {
  return MyGetCompany(In); // Calls the real implementation
}

In my C# code:

[DllImport("TheDLL.dll", EntryPoint = "GetCompany")]
private static extern IntPtr privGetCompany(String In);

// Call this one, not the one above:
public String GetProvince(String In)
{
  return Marshal.PtrToStringAnsi(privGetCompany(In));
}

One final note, if you're running on a 64-bit machine, the 'Any CPU' configuration will make a 64-bit C# executable, which will need a 64-bit DLL. If you only have a 32-bit DLL, you'll need to add a configuration (x86).

The error message you got indicates that your C# program is probably finding the DLL correctly and the function as well, so name mangling is not likely the problem. It sounds like calling convention issue or a problem with the parameter passing.

Marc Bernier