4

i got a native 32 bit dll (no source) which runs as a plugin in an application i use. I've done another native dll myself which will communicate with that plugin in order to create and update the plugin's controls. From that dll i've exported the functions I need in order to control the plugin from my c# application (with p/invoke).

here's the code:

h file:

#pragma once

#include "include\SpoutControls.h"

extern "C" { __declspec(dllexport) void InitializeControls(char *sendername, int *numControls, char** names, int *types, float* floats, float* toggles, float* press, char** text); }
extern "C" { __declspec(dllexport) bool UpdateControls(const char** text, float *floats, float *toggles, float *press, int *numControls); }
extern "C" { __declspec(dllexport) void CloseControls(); }
//
extern "C" __declspec(dllexport) int ReleaseMemory(float *pArray)
{
    delete[] pArray;
    //delete[] Usize;
    return 0;
};

the cpp:

#include "SpoutControls4vvvv.h"

//SpoutControls and the functions
//CreateControl, OpenControls, CheckControls, CloseControls
//are declared in SpoutControls.h, which comes with the 32 bit plugin dll
SpoutControls spoutcontrols;

void InitializeControls(char *sendername, int *numControls, char** names, int *types, float* floats, float* toggles, float* press, char** text) {

    int Vcontrols = numControls[0];
    int Tcontrols = numControls[1];
    int Pcontrols = numControls[2];
    int Scontrols = numControls[3];

    int all = Vcontrols + Tcontrols + Pcontrols + Scontrols;
    int v=0, t=0, p=0, s = 0;

    for (int controlID = 0; controlID < all; controlID++) {

        if (types[controlID] == 0) {
            spoutcontrols.CreateControl(names[controlID], "float",0.0,1.0, floats[v]);
            v++;
        }
        if (types[controlID] == 1) {
            spoutcontrols.CreateControl(names[controlID], "bool", toggles[t]);
            t++;
        }
        if (types[controlID] == 2) {
            spoutcontrols.CreateControl(names[controlID], "event", press[p]);
            p++;
        }
        if (types[controlID] == 3) {
            spoutcontrols.CreateControl(names[controlID], "text", text[s]);
            s++;
        }

    }

    spoutcontrols.OpenControls(sendername);
}


bool UpdateControls(const char** text, float *floats, float *toggles, float *press, int *numControls) {
    int Vcontrols = numControls[0];
    int Tcontrols = numControls[1];
    int Pcontrols = numControls[2];
    int Scontrols = numControls[3];

    int all = Vcontrols + Tcontrols + Pcontrols + Scontrols;
    int v = 0, t = 0, p = 0, s = 0;



    if (spoutcontrols.CheckControls(myControls)) {

        for (int controlID = 0; controlID < all; controlID++) {

            if (myControls[controlID].type == 10) {
                floats[v] = myControls[controlID].value;
                v++;
            }
            if (myControls[controlID].type == 0) {
                toggles[t] = myControls[controlID].value;
                t++;
            }
            if (myControls[controlID].type == 1) {
                press[p] = myControls[controlID].value;
                p++;
            }
            if (myControls[controlID].type == 100) {
                text[s] = myControls[controlID].text.data();
                s++;
            }

        }
        return true;
    }
    return false;
}

void CloseControls() {
    spoutcontrols.CloseControls();
}

and here's the c# code:

public unsafe class SystemSpoutSenderNode: IDisposable
    {

        [System.Runtime.InteropServices.DllImport("SpoutControls4vvvv.dll")]
        private static extern void InitializeControls(IntPtr sendername, IntPtr numControls,String[] names, IntPtr types, IntPtr floats, IntPtr toggles, IntPtr press, String[] text);
        [System.Runtime.InteropServices.DllImport("SpoutControls4vvvv.dll")]
        private static extern int CloseControls();
        [System.Runtime.InteropServices.DllImport("SpoutControls4vvvv.dll", CallingConvention = CallingConvention.Cdecl)]
        private static extern bool UpdateControls([In, Out] String[] text, [In, Out] float[] floats,  [In, Out] float[] toggles, [In, Out] float[] press, IntPtr numControls);
        [System.Runtime.InteropServices.DllImport("SpoutControls4vvvv.dll")]
        private static extern int ReleaseMemory(IntPtr ptr);


    public void Evaluate(int SpreadMax)
            {           
                        //countControls determines number of controls per type (string,float,toggle,click)                                  
                        int[] controls = countControls(FType);
                        //sumControls will just add up all elements in controls
                        int all = sumControls(controls);

                        //in my code these arrays will get filled with values, deleted here for readability         
                        String[] names = new String[all];   
                        int[] types = new int[all]; 
                        float[] floats = new float[controls[0]];
                        float[] toggles = new float[controls[1]];
                        float[] press = new float[controls[2]];

                        String[] text = new String[controls[3]];

                        //initialze return arrays
                        String[] Rtext = new String[controls[3]];
                        float[] Rfloats = new float[controls[0]];
                        float[] Rtoggles = new float[controls[1]];
                        float[] Rpress = new float[controls[2]];

                        //allocate pointers
                        IntPtr SndrNamePtr = NativeUtf8FromString(FSenderName);
                        IntPtr BinPtr = Marshal.AllocHGlobal(4*sizeof(int));                
                        IntPtr TypePtr = Marshal.AllocHGlobal(all*sizeof(int));
                        IntPtr FloatPtr = Marshal.AllocHGlobal(controls[0]*sizeof(float));
                        IntPtr TogglePtr = Marshal.AllocHGlobal(controls[1]*sizeof(float));
                        IntPtr PressPtr = Marshal.AllocHGlobal(controls[2]*sizeof(float));

                        try
                            {           
                            //copy control info + defaults to pointer   
                            Marshal.Copy(controls, 0, BinPtr, 4);
                            Marshal.Copy(types, 0, TypePtr, all);
                            Marshal.Copy(floats, 0, FloatPtr, controls[0]);
                            Marshal.Copy(toggles, 0, TogglePtr, controls[1]);
                            Marshal.Copy(press, 0, PressPtr, controls[2]);

                            //initialize controls   
                            if (FWrite) InitializeControls(SndrNamePtr,BinPtr,names,TypePtr,FloatPtr,TogglePtr,PressPtr,text);

                            //update controls
                            bool changed = UpdateControls(Rtext,Rfloats,Rtoggles,Rpress,BinPtr);


                            //FF, FT, FS and FP are the outputs in my c# host
                            if (changed){

                                for(int j=0; j<controls[0];j++){
                                FF[j]=Rfloats[j];
                                }           
                                for(int j=0; j<controls[1];j++){
                                FT[j]=FloatToBool(Rtoggles[j]);
                                }
                                for(int j=0; j<controls[3];j++){
                                FS[j]=Rtext[j];
                                }
                            }

                            for(int j=0; j<controls[2];j++){
                                FP[j]=FloatToBool(Rpress[j]);
                                }

                            }

                        finally
                        {
                            Marshal.FreeHGlobal(SndrNamePtr);
                            Marshal.FreeHGlobal(BinPtr);
                            Marshal.FreeHGlobal(FloatPtr);
                            Marshal.FreeHGlobal(TogglePtr);
                            Marshal.FreeHGlobal(PressPtr);  
                        }
                    }
                }   
            }

            public void Dispose()
            {
                CleanUp();
                CloseControls();
            }
}

NOTE: the c# code runs without precompiling in a frame-based, c# host environment for graphical programming (vvvv), therefore i've deleted host specific decalarations of inputs (FType,FSenderName) and outputs (FF,FS,FP,FT) to avoid confusion. These will be used to "connect" this code with other functionality. Evaluate will be called every frame by the host.

Now to the actual question(s):

it's working fine so far in 32 bit, but in 64 bit my c# host crashes without any message. after some reading i believe this is due to pointer sizes being different in 32/64bit systems, but i'm not exactly sure what to do/if this actually applies here. I would be very thankful if you could

  • explain me how (and why) to get this code to run in 64 bit
  • point out any other mistakes you might spot along the way- i'm completely new to c++ and still a beginner in c#, so i'm pretty confident there's a lot to improve here; especially: memory leaks and passing the values from c++ to c# and vice versa...uiuiui.

I've understood that I shouldn't cast a pointer to an int in 64 bit, so the last thing I've tried is to change from

int Vcontrols = numControls[0];
int Tcontrols = numControls[1];
int Pcontrols = numControls[2];
int Scontrols = numControls[3];

to

int Vcontrols = (INT_PTR)numControls[0];
int Tcontrols = (INT_PTR)numControls[1];
int Pcontrols = (INT_PTR)numControls[2];
int Scontrols = (INT_PTR)numControls[3];

but with no luck, therefore I'm posting my original problem, even if this is a correct improvement(?).

EDIT: thanks to @dkackman for pointing out one unclear point: my cpp code calls functions which come as source code (SpoutControls.h) with the native 32 bit dll. It's not the source for the 32 bit dll itself but declares the functions used to (as far as i can tell) access the same shared memory as the 32 bit dll. I can also copy paste the code here if this might be the problem? Also can be found here

thank you.

  • 2
    Why do you need to pass pointers at all? That just results in a lot of tedious, error-prone code. – Cody Gray - on strike Aug 28 '16 at 14:08
  • it's the only way i found (to work) to pass arrays of floats/doubles/strings between managed and unmanaged code. can you elaborate please/guide me in the right direction, what am I missing? – digitalWannabe Aug 28 '16 at 14:19
  • as dkackman indicated below, it's not possible to load a 32bit DLL into a 64bit process. You'll either have to create a separate 32 process on 64bit to load that 32bit DLL and do ipc between the two processes or (much easier) compile your C# code for 32bits ONLY so that it runs 32bit even on 64bit OS. – GreatAndPowerfulOz Aug 28 '16 at 15:12
  • please see my edit following dkackmans post: the 32 bit dll does get loaded by a 32 bit application and values are passed via shared memory. also, a similar version of the c# code (which shares textures instead of values) is working fine in both x64 and x32 – digitalWannabe Aug 28 '16 at 15:29
  • @CodyGray thanks to your comment i found this: https://msdn.microsoft.com/en-us/library/hk9wyw21(v=vs.110).aspx and changed all my pointer/marshalling code in c# to just use arrays accordingly. thanks for that hint, makes it all a lot shorter. however, on the c++ side this still uses pointers + it doesn't change anything, still crashes. – digitalWannabe Aug 28 '16 at 17:42
  • 1
    Your mistake is probably elsewhere. Perhaps in the shared memory code. You didn't show that. Show a [mcve]. – David Heffernan Aug 29 '16 at 07:20
  • @DavidHeffernan does this mean you can not see a problem with my code? I can not provide a minimal example easily, as I'm including code by somebody else (linked above)....I've contacted the original author to help...thanks – digitalWannabe Aug 31 '16 at 22:53

1 Answers1

6

I am afraid you are out of luck. If your process is 64bit, you won't be able to load that 32bit dll, no matter how much you try.

Can I load a 32 bit DLL into a 64 bit process on Windows?

from https://msdn.microsoft.com/en-us/library/windows/desktop/aa384231(v=vs.85).aspx

On 64-bit Windows, a 64-bit process cannot load a 32-bit dynamic-link library (DLL).

Without access to its source, your only option would be to convert your host to 32bit or otherwise figure out how to host the 32bit plugin in a 32bit process and use some sort of IPC to communicate with it from a 64bit host process.

So my guess is that this has nothing to do with your wrapper, array passing or interop code.

Community
  • 1
  • 1
dkackman
  • 15,179
  • 13
  • 69
  • 123
  • sorry if this was unclear: the native 32 bit dll does get loaded fine in it's host application as a plugin, I do not need to load it in my c# 64 bit application. I think the problem occurs when the c# 64 bit code passes values via pointers to the c++ 64bit dll, which itself (afaik) writes the values to sharedmemory for the 32bit dll to access. will update my question to make this clearer. thanks – digitalWannabe Aug 28 '16 at 14:31
  • 1
    Can you clarify: Is your host process 64 or 32 bit? If so, there will be no way to load that 32bit dll into it. If not, I am not clear as to where references to 64 bit are coming from. A 32bit process, even if running on a 64 bit OS, is still a 32bit process. You cannot mix the bitness of compiled binaries in the same process. – dkackman Aug 28 '16 at 15:13
  • i'm running the 32 bit dll in a 32 bit application. always. i want it to communicate with another (c#) application, which can either run in 32bit or 64 bit. this application runs the above c# code and calls the dll I did (c++ from above, compiled to either 32bit or 64bit). but the 64bit version crashes the c# host. note that I know that in theory this should work. there is a similar plugin for my c# host which shares textures instead of values, works both in 32/64bit on the c# side and 32 bit on the receiving side. – digitalWannabe Aug 28 '16 at 15:39
  • So that other C# application (that can run either 32 or 64) will only ever be able to load the 32bit dll when running as a 32bit process. Does it work when the other C# application is running 32bit? – dkackman Aug 28 '16 at 19:32
  • if the c# app, let's call it app B, is running in 64 bit i'm loading my native 64 bit dll (code above) to communicate with app A. If app B is running in 32 bit i'm loading my native 32 bit dll (code above). App A always is 32 bit and can only load the native 32 bit dll which is not mine, no source. If app B is running in 32 bit it works, if app B is running in 64 bit app B crashes with no effect in app A. – digitalWannabe Aug 28 '16 at 19:55
  • as mentioned before, another code part of my native dll loaded from the same c# app B successfully is sharing a texture from app B to the same 32 bit dll in app A, no matter if app B is running in 32 bit or 64 bit (loading 32/64 bit versions of my native dll accordingly). Getting values back from app A to app B, which is what i'm trying here, crashes app B in 64 bit, but works in 32 bit. – digitalWannabe Aug 28 '16 at 20:02