배은정
배은정

Reputation: 11

C# Marshalling (C# call 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:

unsigned long makeArray(unsigned char* sendArr, unsigned long sendArrLen, unsigned char *recvArr, unsigned long *recvArrLen);

I wrote the following code in C#:

[DllImport("test.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern ulong makeArray(byte[] sendArr, ulong sendArrLen, byte[] recvArr, ulong recvArrLen);

    private byte[] MakeArray()
    {
        byte[] arrSend = new byte[] { 0x00, 0x12, 0x34 };

        ulong nRecvArrLen = 0;
        byte[] arrRecv = null; // assign in c++ dll function (variable size)

        if(makeArray(arrSend, (ulong)arrSend.Length, arrRecv, nRecvArrLen) == 1)
        {
            return arrRecv;
        }

        return null;
    }

Unfortunately, the above code is not working... May I know how can I pass a pointer-to-pointer to the C++ func? If it is not possible, is there any workaround?

Thank you.

Upvotes: 1

Views: 1880

Answers (2)

tommybee
tommybee

Reputation: 2551

Where is your 'test.dll'? I think it is a path problem...

The file must be located at the one of following directories..

[%SystemRoot%] (Windows directory)
[%SystemRoot%]\system32\(32 bit)   or 
[%SystemRoot%]\sysWOW64\(64 bit)
The same location with your executable file
PATH variable

Or it can be a type mismatch ... refer to the [site].

I matched the ulong type of csharp to unsigned __int64 in c/c++ on windows.

Declaration of the C# code is a little bit changed.

[DllImport(@"testdll.dll", CallingConvention = CallingConvention.Cdecl)]
    static extern ulong makeArray
    (
     byte[] sendArr, 
     ulong sendArrLen, 
     [Out] byte[] recvArr, 
     ref ulong recvArrLen
     );

Here are the testdll.cpp abd testdll.h i tested

#include "testdll.h"

unsigned __int64 makeArray(
    unsigned char* sendArr, 
    unsigned __int64 sendArrLen, 
    unsigned char *recvArr, 
    unsigned __int64 *recvArrLen
)
{
    int i;

    for(i=0; i < sendArrLen; i++)
    {
        recvArr[i] = sendArr[i];
    }

    memcpy(recvArrLen, &sendArrLen, sizeof(unsigned __int64));

    return i;
}

testdll.h code.

#pragma once

#ifdef EXPORT_TESTDLL
#define TESTDLL_API __declspec(dllexport) 
#else
#define TESTDLL_API __declspec(dllimport) 
#endif

extern "C" TESTDLL_API unsigned __int64 makeArray(
    unsigned char* sendArr, 
    unsigned __int64 sendArrLen, 
    unsigned char *recvArr, 
    unsigned __int64 *recvArrLen
);

Finally, C# code of console application as follows, call the native dll function in c++ - testdll.dll print items on the console.

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

namespace ConsoleApplication2
{
    class Program
    {

        [DllImport(@"testdll.dll", CallingConvention = CallingConvention.Cdecl)]
        static extern ulong makeArray(byte[] sendArr, ulong sendArrLen, [Out] byte[] recvArr, ref ulong recvArrLen);

        static byte[] MakeArray()
        {
            byte[] arrSend = new byte[] { 0x00, 0x12, 0x34 };
            ulong nRecvArrLen = 0;
            ulong ret = 0;
            byte[] arrRecv = new byte[3]; // assign in c++ dll function (variable size)

            try
            {
                if ((ret = makeArray(arrSend, (ulong)arrSend.Length, arrRecv, ref nRecvArrLen)) > 0)
                {
                    if(arrRecv != null)
                        Console.WriteLine("nRecvArrLen2============>" + arrRecv.Length);


                    return arrRecv;
                }

            }
            catch (DllNotFoundException dne)
            {
                Console.WriteLine("============> dll not found....");
            }


            return null;
        }


        static void Main(string[] args)
        {
            byte[] retbytes = MakeArray();

            if (retbytes != null)
            {
                Console.WriteLine("=====LEN=======>" + retbytes.Length);

                for (int i = 0; i < retbytes.Length; i++)
                    Console.WriteLine("====ITEM========>" + retbytes[i]);
            }
            else
                Console.WriteLine("=====NULL=======>");


        }
    }
}

enter image description here

Upvotes: 1

Mr.C64
Mr.C64

Reputation: 42924

unsigned long in MSVC is a 32-bit unsigned integer, so you should map it to the System.UInt32 .NET type, corresponding to the uint keyword in C#.

C# ulong is an unsigned 64-bit integer, corresponding to MSVC's unsigned __int64 or unsigned long long.

The unsigned long *recvArrLen parameter should me mapped using ref in the C# PInvoke declaration, as you have a level of indirection via pointer.

It also seems that the arrRecv array parameter should be allocated by the caller (C# in your case), and filled by the DLL function.

If the DLL function allocates the buffer, you should add another level of indirection (unsigned char **recvArr), and you should provide a way to release the allocated memory (e.g. the DLL should export a release function as well).

I would try with something like this for PInvoke:

[DllImport("test.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern uint makeArray(
    byte[] sendArr, 
    uint sendArrLen, 
    [Out] byte[] recvArr, 
    ref uint recvArrLen
);

Upvotes: 2

Related Questions