2

I am trying to return the byte array from my unmanaged c++ dll back to c# unity. Thank you very much in advance for taking the time to help >< I'm really new to DLL in unity so I'm extremely confused how 2 languages are even suppose to work together.

CPP

The problem is in here, I've done my calculations, but I'm struggling to find a way to return it back to c# with an array format.

Currently, the byte array holds color codes e.g RGBA (223,124,23,255,212,143,234,255) and it repeats

#include "WebcamDLL.h"
#include <vector>

extern "C" {
int adjustBrightnesss(unsigned char* bytes, int sizeOfArray)
{
    std::vector<int> myvector;
    int alphaP = 0;
    for (int i = 0; i < sizeOfArray; i++) {
        switch (alphaP) {
        case 0:
        case 1:
        case 2:
            myvector[i] = bytes[i] / 2;
            alphaP++;
            break;
        case 3:
            alphaP = 0;
            break;
        }
    }
    return bytes;
}
}

Header file

#ifdef TESTFUNCDLL_EXPORT
#define TESTFUNCDLL_API __declspec(dllexport) 
#else
#define TESTFUNCDLL_API __declspec(dllimport) 
#endif

extern "C" {
    TESTFUNCDLL_API int adjustBrightnesss(unsigned char* bytes, int sizeOfArray);
}

C# File

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using System.Runtime.InteropServices; // Needed for custom DLL
using System;

public class WebcamManager : MonoBehaviour
{
    private RawImage ri; // Gets the RawImage component from script parent
    private WebCamTexture wct; // Object to hold the WebCamTexture add-on
    private AspectRatioFitter arf; // Ensures RawImage object has same scaling as Webcam
    private Color32[] pixels; // Keeps the pixels from webcamtexture
    Texture2D tex; // A placeholder for the texture2D

    [DllImport("WebcamBrightness", EntryPoint = "adjustBrightness")]
    public static extern int adjustBrightness(byte bytes, int b);

    void Start()
    {
        newBrightness = 1; // Default 1, if 0 will make image go super bright
        arf = GetComponent<AspectRatioFitter>(); // Gets the component AspectRatioFitter in script parent
        ri = GetComponent<RawImage>(); // Gets the component RawImage in script parent
        wct = new WebCamTexture(Screen.width, Screen.height); // Creates a new WebCamTexture in wct with the width and height of the current screen
        wct.Play(); // plays webcam

        //adjustBrightness(wct.GetPixels32());
    }

    // Update is called once per frame
    void Update()
    {
        float videoRatio = (float)wct.width / (float)wct.height; // Ensures that the scaling is the same as the webcam
        arf.aspectRatio = videoRatio; // applies webcam scaling to rawimage

        tex = new Texture2D(wct.width, wct.height, TextureFormat.ARGB32, false); // pass texture2D tex the information of webcamtexture height, width, ARGB32 (32 bit with alpha) and no mipmaps

        pixels = wct.GetPixels32();

// After getting the bytes, I wanna save it back to color32 or atleast an array format.

        tex.SetPixels32(pixels);
        tex.Apply();

        // Sets texture of rawimage from canvas to be web camera view
        ri.texture = tex;
    }
}

EDITTED CPP FILE

#include "WebcamDLL.h"
#include <vector>

extern "C" {
    unsigned char* adjustBrightness(unsigned char* bytes, int sizeOfArray)
    {
        int alphaP = 0;
        for (int i = 0; i < sizeOfArray; i++) {
            switch (alphaP) {
            case 0:
            case 1:
            case 2:
                bytes[i] = bytes[i] / 2;
                alphaP++;
                break;
            case 3:
                alphaP = 0;
                break;
            }
        }
        return bytes;
    }

    int freeMem(unsigned char* arrayPtr) {
        delete[] arrayPtr;
        return 0;
    }
}

EDITTED HEADER FILE

#ifdef TESTFUNCDLL_EXPORT
#define TESTFUNCDLL_API __declspec(dllexport) 
#else
#define TESTFUNCDLL_API __declspec(dllimport) 
#endif

extern "C" {
    TESTFUNCDLL_API unsigned char* adjustBrightness(unsigned char* bytes, int sizeOfArray);
    TESTFUNCDLL_API int freeMem(unsigned char* arrayPtr);
}

EDITTED C# FILE

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using System.Runtime.InteropServices; // Needed for custom DLL
using System;

public class WebcamManager : MonoBehaviour
{
    private RawImage ri; // Gets the RawImage component from script parent
    private WebCamTexture wct; // Object to hold the WebCamTexture add-on
    private AspectRatioFitter arf; // Ensures RawImage object has same scaling as Webcam
    private Color32[] pixels; // Keeps the pixels from webcamtexture
    Texture2D tex; // A placeholder for the texture2D
    public RawImage ri2;

    float timer;

    [DllImport("WebcamBrightness", EntryPoint = "adjustBrightness", CallingConvention = CallingConvention.Cdecl)]
    public static extern IntPtr adjustBrightness(byte[] bytes, int b);

    [DllImport("WebcamBrightness", EntryPoint = "freeMem", CallingConvention = CallingConvention.Cdecl)]
    public static extern int freeMem(IntPtr ptr);

    [SerializeField]
    int newBrightness;

    void Start()
    {
        newBrightness = 1; // Default 1, if 0 will make image go super bright
        arf = GetComponent<AspectRatioFitter>(); // Gets the component AspectRatioFitter in script parent
        ri = GetComponent<RawImage>(); // Gets the component RawImage in script parent
        wct = new WebCamTexture(Screen.width, Screen.height); // Creates a new WebCamTexture in wct with the width and height of the current screen
        wct.Play(); // plays webcam

        float videoRatio = (float)wct.width / (float)wct.height; // Ensures that the scaling is the same as the webcam
        arf.aspectRatio = videoRatio; // applies webcam scaling to rawimage
        tex = new Texture2D(wct.width, wct.height, TextureFormat.ARGB32, false); // pass texture2D tex the information of webcamtexture height, width, ARGB32 (32 bit with alpha) and no mipmaps
    }

    // Update is called once per frame
    void Update()
    {
        //timer += Time.deltaTime;

        pixels = wct.GetPixels32();

        IntPtr returnedPtr = adjustBrightness(Color32ArrayToByteArray(pixels), Color32ArrayToByteArray(pixels).Length);

        byte[] returnedResult = new byte[Color32ArrayToByteArray(pixels).Length];

        Marshal.Copy(returnedPtr, returnedResult, 0, Color32ArrayToByteArray(pixels).Length);

        freeMem(returnedPtr);

        Debug.Log(returnedResult[0]);

        tex.SetPixels32(pixels);

        ri.texture = tex;

        tex.Apply();

        // Sets texture of rawimage from canvas to be web camera view
    }

    public void AdjustBrightness(float b)
    {
        newBrightness = (int)b;
    }

    private static byte[] Color32ArrayToByteArray(Color32[] colors)
    {
        if (colors == null || colors.Length == 0)
            return null;

        int lengthOfColor32 = Marshal.SizeOf(typeof(Color32));
        int length = lengthOfColor32 * colors.Length;
        byte[] bytes = new byte[length];

        GCHandle handle = default(GCHandle);
        try
        {
            handle = GCHandle.Alloc(colors, GCHandleType.Pinned);
            IntPtr ptr = handle.AddrOfPinnedObject();
            Marshal.Copy(ptr, bytes, 0, length);
        }
        finally
        {
            if (handle != default(GCHandle))
                handle.Free();
        }
        return bytes;
    }
}
Haru The Potato
  • 97
  • 1
  • 10

2 Answers2

7

There are many ways to return byte arrays from C# and below is one of them. The memory allocation and de-allocation are both done in C++. You must call the function to free the memory from C#. I made the example very simple so that you can easily integrate it in your current code.

IntPtr is the key in this answer.

C++:

char* getByteArray() 
{
    //Create your array(Allocate memory)
    char * arrayTest = new char[2];

    //Do something to the Array
    arrayTest[0]=3;
    arrayTest[1]=5;

    //Return it
    return arrayTest;
}


int freeMem(char* arrayPtr){
    delete[] arrayPtr;
    return 0;
}

C#:

[DllImport("Test.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr getByteArray();

[DllImport("Test.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int freeMem(IntPtr ptr);

//Test
void Start() {
 //Call and return the pointer
 IntPtr returnedPtr = getIntArray();

 //Create new Variable to Store the result
 byte[] returnedResult = new byte[2];

 //Copy from result pointer to the C# variable
 Marshal.Copy(returnedPtr, returnedResult, 0, 2);

 //Free native memory
 freeMem(returnedPtr);

 //The returned value is saved in the returnedResult variable
 byte val1 = returnedResult[0];
 byte val2 = returnedResult[1];
}
Programmer
  • 121,791
  • 22
  • 236
  • 328
  • Hi!, Thank you very much for the help! I seem to have managed to bring the byte array back using the solution you've given, but I'm having troubles with the freemem function, whenever I uncommented the delete[] arrayPtr;, it seems to crash my unity without giving any errors. I've edited my post with the new code, I feel like there was afew parts that I messed up trying to integrate over > – Haru The Potato Apr 26 '17 at 23:26
  • Once you call `freeMem(returnedPtr);` you can't and should not use the `returnedPtr` variable again until it initialized again with `IntPtr returnedPtr = getIntArray();`. The result is now in `returnedResult`. Another thing to do is to check if the pointer is null on the C++ side before deleting it. Add `if (arrayPtr!= nullptr){delete[] arrayPtr;}` to your `freeMem` function. Let me know if that solves your problem. – Programmer Apr 26 '17 at 23:33
  • Oh! I get it, so freemem is only called when I want to disable my webcam(stop using the bytes) and is reinitialized when I want to use it again. Is that right? – Haru The Potato Apr 27 '17 at 01:02
  • Not really. I noticed there is a difference between my C++ code `getByteArray` function and the one you just modified into your question. I did `new byte[2];` but you did not. You seem to be modifying the variable you passed into the function instead. in C++, if you use the `new` keyword, you must use the `delete` keyword. This is why C+ is complicated. I think you should learn C++ before working on this project. You will save your self so many bugs you will run into. See [here](https://www.tutorialspoint.com/cplusplus/cpp_dynamic_memory.htm) for more info. – Programmer Apr 27 '17 at 01:24
  • With the current code you have, you will run into problems since you are modifying the array you passed in. The purpose of my answer is to create new array `byte[] returnedResult = new byte[sizeOfArray];` then copy the array that is passed in to that new local variable `std::memcpy(returnedResult, bytes, sizeOfArray);`. Now modify that cop and return it. You can then call `freeMem` after from C#. – Programmer Apr 27 '17 at 01:31
  • Finally, if you want to modify the variable you passed it without creating new local copy of it, you **must** use `[In, Out]`. See [here](http://stackoverflow.com/questions/14366066/are-p-invoke-in-out-attributes-optional-for-marshaling-arrays) for why. I am saying this now because the code you now may work today, sometimes, it won't. That's the thing. – Programmer Apr 27 '17 at 01:36
  • Thanks for your concerns! I really am bad at C++ I have to say, especially since I don't have much of a clue what mindset I must have before learning C++, since pointers sometimes really doesn't make sense to me at all, I'll take your advice and improve my knowledge and skills. > – Haru The Potato Apr 27 '17 at 02:03
0

You could pass an extra parameter to the function, let's say another byte array, and then in the adjustBrightnesss function replace that myvector with that array. As far as i know you will get the array with the modified values

Gerard097
  • 815
  • 4
  • 12
  • Hi, Thanks for the help! but doesn't the extra parameter only acts as a way to bring items into a function? the weird problem is that I'm able to return back a singular int, or float, but not an array or vector. or atleast I don't know how to haha > – Haru The Potato Apr 26 '17 at 23:34