Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C# call C++ DLL passing pointer-to-pointer argument

Tags:

c++

c#

dll

Could you guys please help me solve the following issue? I have a C++ function dll, and it will be called by another C# application. One of the functions I needed is as follow:

struct DataStruct
{
    unsigned char* data;
    int len;
};

DLLAPI int API_ReadFile(const wchar_t* filename, DataStruct** outData);

I wrote the following code in C#:

class CS_DataStruct
{
    public byte[] data;
    public int len;
}

[DllImport("ReadFile.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
private static extern int API_ReadFile([MarshalAs(UnmanagedType.LPWStr)]string filename, ref CS_DataStruct data);

Unfortunately, the above code is not working... I guess that is due to the C++ func takes a pointer-to-pointer of DataStruct, while I just passed a reference of CS_DataStruct in.

May I know how can I pass a pointer-to-pointer to the C++ func? If it is not possible, is there any workaround? (the C++ API is fixed, so changing API to pointer is not possible)

Edit: Memory of DataStruct will be allocated by c++ function. Before that, I have no idea how large the data array should be. (Thanks for the comments below)

like image 725
LennonLam Avatar asked Dec 06 '13 08:12

LennonLam


People also ask

What C is used for?

C programming language is a machine-independent programming language that is mainly used to create many types of applications and operating systems such as Windows, and other complicated programs such as the Oracle database, Git, Python interpreter, and games and is considered a programming foundation in the process of ...

Is C language easy?

Compared to other languages—like Java, PHP, or C#—C is a relatively simple language to learn for anyone just starting to learn computer programming because of its limited number of keywords.

What is C in C language?

What is C? C is a general-purpose programming language created by Dennis Ritchie at the Bell Laboratories in 1972. It is a very popular language, despite being old. C is strongly associated with UNIX, as it was developed to write the UNIX operating system.

What is the full name of C?

In the real sense it has no meaning or full form. It was developed by Dennis Ritchie and Ken Thompson at AT&T bell Lab. First, they used to call it as B language then later they made some improvement into it and renamed it as C and its superscript as C++ which was invented by Dr.


2 Answers

I used the following test implementation:

int API_ReadFile(const wchar_t* filename, DataStruct** outData)
{
    *outData = new DataStruct();
    (*outData)->data = (unsigned char*)_strdup("hello");
    (*outData)->len = 5;
    return 0;
}

void API_Free(DataStruct** pp)
{
    free((*pp)->data);
    delete *pp;
    *pp = NULL;
}

The C# code to access those functions are as follows:

[StructLayout(LayoutKind.Sequential)]
struct DataStruct
{
    public IntPtr data;
    public int len;
};

[DllImport("ReadFile.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
unsafe private static extern int API_ReadFile([MarshalAs(UnmanagedType.LPWStr)]string filename, DataStruct** outData);

[DllImport("ReadFile.dll", CallingConvention = CallingConvention.Cdecl)]
unsafe private static extern void API_Free(DataStruct** handle);

unsafe static int ReadFile(string filename, out byte[] buffer)
{
    DataStruct* outData;
    int result = API_ReadFile(filename, &outData);
    buffer = new byte[outData->len];
    Marshal.Copy((IntPtr)outData->data, buffer, 0, outData->len);
    API_Free(&outData);
    return result;
}

static void Main(string[] args)
{
    byte[] buffer;
    ReadFile("test.txt", out buffer);
    foreach (byte ch in buffer)
    {
        Console.Write("{0} ", ch);
    }
    Console.Write("\n");
}

The data is now transferred to buffer safely, and there should be no memory leaks. I wish it would help.

like image 156
Yongwei Wu Avatar answered Sep 29 '22 08:09

Yongwei Wu


It isn't necessary to use unsafe to pass a pointer to an array from a DLL. Here is an example (see the 'results' parameter). The key is to use the ref attribute. It also shows how to pass several other types of data.

As defined in C++/C:

#ifdef __cplusplus
extern "C" {
#endif

#ifdef BUILDING_DLL
#define DLLCALL __declspec(dllexport)
#else
#define DLLCALL __declspec(dllimport)
#endif

static const int DataLength = 10;
static const int StrLen = 16;
static const int MaxResults = 30;

enum Status { on = 0, off = 1 };

struct Result {
    char name[StrLen]; //!< Up to StrLen-1 char null-terminated name
    float location;  
    Status status;
};

/**
* Analyze Data
* @param data [in] array of doubles
* @param dataLength [in] number of floats in data
* @param weight [in]
* @param status [in] enum with data status
* @param results  [out] array of MaxResults (pre-allocated) DLLResult structs.
*                    Up to MaxResults results will be returned.
* @param nResults  [out] the actual number of results being returned.
*/
void DLLCALL __stdcall analyzeData(
      const double *data, int dataLength, float weight, Status status, Result **results, int *nResults);

#ifdef __cplusplus
}
#endif

As used in C#:

private const int DataLength = 10;
private const int StrLen = 16;
private const int MaxThreatPeaks = 30;

public enum Status { on = 0, off = 1 };

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct Result
{
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = StrLen)] public string name; //!< Up to StrLen-1 char null-terminated name 
    public float location;  
    public Status status;       
}

[DllImport("dllname.dll", CallingConvention = CallingConvention.StdCall, EntryPoint = "analyzeData@32")] // "@32" is only used in the 32-bit version.
public static extern void analyzeData(
    double[] data,
    int dataLength, 
    float weight, 
    Status status,
    [MarshalAs(UnmanagedType.LPArray, SizeConst = MaxResults)] ref Result[] results, 
    out int nResults
);

Without the extern "C" part, the C++ compiler would mangle the export name in a compiler dependent way. I noticed that the EntryPoint / Exported function name matches the function name exactly in a 64-bit DLL, but has an appended '@32' (the number may vary) when compiled into a 32-bit DLL. Run dumpbin /exports dllname.dll to find the exported name for sure. In some cases you may also need to use the DLLImport parameter ExactSpelling = true. Note that this function is declared __stdcall. If it were not specified, it would be __cdecl and you'd need CallingConvention.Cdecl.

Here is how it might be used in C#:

Status status = Status.on;
double[] data = { -0.034, -0.05, -0.039, -0.034, -0.057, -0.084, -0.105, -0.146, -0.174, -0.167};
Result[] results = new Result[MaxResults];
int nResults = -1; // just to see that it changes (input value is ignored)
analyzeData(data, DataLength, 1.0f, status, ref results, out nResults);
like image 45
jtbr Avatar answered Sep 29 '22 06:09

jtbr