0

I have a function in a native dll whose function looks like this

int GetActiveNames(char* names[]);

How do I create my PInvoke wrapper method in C# to the above function which returns me an array of strings/characters?


To be more descriptive..

I tried the P/Invoke way and its showing the following error Attempted to read or write protected memory. This is often an indication that other memory is corrupt..

I just stripped down the actual C function signature to make it simple. But here is the complete C function signature and the description of the parameters of the function

 int OpalGetActiveProjects(unsigned short allocatedProjects,
unsigned short *numProjects,
unsigned short allocatedModels,
unsigned short *numModels,
unsigned short allocatedProjectsPathLen,
unsigned short *maxProjectPathLen,
unsigned short allocatedModelPathLen,
unsigned short *maxModelPathLen,
unsigned short allocatedMachineNameLen,
unsigned short *maxMachineNameLen,
unsigned short allocatedIpLen,
unsigned short *maxIpLen,
char *projectPaths[],
unsigned short instanceIDs[],
char *machineNames[],
char *controllerIPs[],
char *modelPaths[],
unsigned short numModelsPerProject[]
unsigned short modelIDs[],
unsigned short modelStates[]);

The function returns a list of active projects. For each project, this function returns the full path of the configuration file, the name and the IP address of the machine that opened this project, the instance ID of the current project session, and the list of models belonging to this project. For each model, path, ID and state are returned.If the allocated storage is too small for any of the arguments, none of the lists are returned, but the numProjects, numModels, maxProjectPathLen, maxModelPathLen, maxMachineNameLen and maxIpLen values are set, and the return value is E2BIG. An application can use this fact to specify lengths of 0 on a first call, allocate the required storage and issue a second call to get the information.


Input parameters


allocatedProjects: number of projects for which storage was allocated by the caller for the paths, machine name, IP addresses and instance ids. allocatedModels: number of models for which storage was allocated by the caller for the names, paths and instance ids. allocatedProjectPathLen: allocated length of storage to receive the project paths allocatedModelPathLen: allocated length of storage to receive the model paths allocatedMachineNameLen: allocated length of storage to receive the machine names allocatedIpLen: allocated length of storage to receive the IP addresses


Output parameters


numProjects: pointer to where the API will store the actual number of active projects. numModels: pointer to where the API will store the actual total number of models for all active projects. maxProjectPathLen: pointer to where the API will store the length of the longest project path. maxModelPathLen: pointer to where the API will store the length of the longest model path. maxMachineNameLen: pointer to where the API will store the length of the longest machine name. maxIpLen: pointer to where the API will store the length of the longest IP address. projectPaths: array of strings where the API will store the paths of the active projects’ configuration files. instanceIDs: array where the API will store the instance Ids of the active projects. machineNames: array where the API will store the machine names. controllerIPs: array where the API will store the IP adresses. modelPaths: array where the API will store the model paths of all active projects. numModelsPerProject: array where the API will store the number of models for each project. Use these values to determine what model belongs to what project. modelIDs: array where the API will store the model IDs for all active projects. modelStates: array where the API will store the model states for all active projects.

There is also a C program which uses the OpalGetActiveProjects() function to get the projectPaths, machineNames, etc..

#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
#include <string.h>

// Header for getcwd() is platform-dependent
#if defined(WIN32)
#include <direct.h>
#include <windows.h>
#elif defined(__linux__)
#include <unistd.h>
#define _MAX_PATH 256
 #endif
 #include "OpalApi.h"

void    PrintError(int rc, char *funcName);

 #define FALSE 0

 #define TRUE 1

 int main (int argc, char* argv[])
 {
     char                                **projectPath, **modelPath;
     char                                *projectPaths, *modelPaths;
     char                                **loaderMachineName, **controllerIp;
     char                                *loaderMachineNames, *controllerIps;
     int                                         i, rc, projectIdx;
     unsigned short                  *instId;
     int                             *instId2;
     int                                 * modelIds;
     unsigned short             *numModelsPerProject;
     unsigned short             *modelId;
     unsigned short             *modelState;
     unsigned short             allocProjects, numProjects;
     unsigned short             allocModels, numModels;
     unsigned short             allocProjectPathLen, maxProjectPathLen;
     unsigned short             allocModelPathLen, maxModelPathLen;
     unsigned short             allocMachineLen, maxMachineLen;
     unsigned short             allocIpLen, maxIpLen;

     // Obtenir la taille des données.
     allocProjects              =allocModels                 = 0;
     allocProjectPathLen = allocModelPathLen = 0;
     allocMachineLen         = allocIpLen            = 0;

     rc = OpalGetActiveProjects( 0, &allocProjects,
                                                             0, &allocModels,
                                                             0, &allocProjectPathLen,
                                                             0, &allocModelPathLen,
                                                             0, &allocMachineLen,
                                                             0, &allocIpLen,
                                                             NULL, NULL, NULL, NULL,
                                                             NULL, NULL, NULL, NULL );

     if ((rc != 0) && (rc != E2BIG))
     {
         PrintError(rc, "OpalGetActiveProject");
            printf(" -        alGetActiveProjects error output !!!\n");
            printf("\t numProjects - %i \n", allocProjects);
            printf("\t numModels - %i \n", allocModels);
            printf("\t maxProjectPathLen - %i \n", allocProjectPathLen);
            printf("\t maxModelPathLen - %i \n", allocModelPathLen);
            //printf("\t maxMachineNameLen - %i \n", maxMachineNameLen);
            printf("\t maxIpLen - %i \n", allocIpLen);
            return (rc);
             // Erreur
             //return returnInstance;
     }

     projectPath = (char **)_alloca(allocProjects * sizeof(*projectPath));
     projectPaths = (char *)_alloca(allocProjects * (allocProjectPathLen + 1) * sizeof(*projectPaths));

     loaderMachineName = (char **)_alloca(allocProjects * sizeof(*loaderMachineName));
     loaderMachineNames = (char *)_alloca(allocProjects * (allocMachineLen + 1) * sizeof(*loaderMachineNames));

     controllerIp = (char **)_alloca(allocProjects * sizeof(*controllerIp));
     controllerIps = (char *)_alloca(allocProjects * (allocIpLen + 1) * sizeof(*controllerIps));

     numModelsPerProject = (unsigned short*)_alloca(allocProjects * sizeof(*numModelsPerProject));

     modelPath = (char **)_alloca(allocModels * sizeof(*modelPath));
     modelPaths = (char *)_alloca(allocModels * (allocModelPathLen + 1) * sizeof(*modelPaths));

     for (i = 0; i < allocProjects; ++i)
     {
             projectPath[i]                          = &projectPaths[i * (allocProjectPathLen + 1)];
             loaderMachineName[i]        = &loaderMachineNames[i * (allocMachineLen + 1)];
             controllerIp[i]                 = &controllerIps[i * (allocIpLen + 1)];
     }

     for (i = 0; i < allocModels; ++i)
     {
             modelPath[i] = &modelPaths[i * (allocModelPathLen + 1)];
     }

     instId = (unsigned short *)_alloca(allocProjects * sizeof(*instId));
     instId2 = (int *)_alloca(allocProjects * sizeof(*instId2));
     modelId = (unsigned short*)_alloca(allocModels * sizeof(*modelId));
     modelState = (unsigned short *)_alloca(allocModels * sizeof(*modelState));

     rc = OpalGetActiveProjects( allocProjects, &numProjects,
                                                             allocModels, &numModels,
                                                             allocProjectPathLen, &maxProjectPathLen,
                                                             allocModelPathLen, &maxModelPathLen,
                                                             allocMachineLen, &maxMachineLen,
                                                             allocIpLen, &maxIpLen,
                                                             projectPath, instId,
                                                             loaderMachineName, controllerIp, modelPath,
                                                             numModelsPerProject, modelId, modelState);
     printf(" - OpalGetActiveProjects output !!!\n");
            printf("\t numProjects - %i \n", allocProjects);
            printf("\t numModels - %i \n", allocModels);
            printf("\t maxProjectPathLen - %i \n", allocProjectPathLen);
            printf("\t maxModelPathLen - %i \n", allocModelPathLen);
            //printf("\t maxMachineNameLen - %i \n", maxMachineNameLen);
            printf("\t maxIpLen - %i \n", allocIpLen);

    for(i=0; i<numProjects; i++)
    {
        printf("\t \t projectPath[%d] = %s \n", i, *(projectPath +i));
        printf("\t \t instId[%d] = %d \n", i, *(instId + i));
        printf("\t \t loaderMachineName[%d] = %s \n", i, *(loaderMachineName + i));
        printf("\t \t controllerIp[%d] = %s \n", i, *(controllerIp + i));
        printf("\t \t numModelsPerProject[%d] = %d \n", i, *  (numModelsPerProject +i));
    }

    for(i=0; i<numModels; i++)
    {
        printf("\t \t modelPath[%d] = %s \n", i, *(modelPath+i));
        printf("\t \t modelId[%d] = %d \n", i, *(modelId +i));
        printf("\t \t modelState[%d] = %d \n", i, *(modelState+i));
    }
    OpalDisconnect();
    getchar();
 }


 void   PrintError(int rc, char *funcName)
  {
char            buf[512];
unsigned short  len;

OpalGetLastErrMsg(buf, sizeof(buf), &len);
printf("Error !!! \n %s (code %d) from %s\n", buf, rc, funcName);
  }
BenMorel
  • 34,448
  • 50
  • 182
  • 322
  • 1
    You can't, no way to properly destroy the returned strings after using them. This function is not safe to use in native code either so using C++/CLI is a crap shoot as well. – Hans Passant Sep 19 '12 at 12:29

3 Answers3

1

The signature is too weird to be translated into C#. I tried seeing what TlbImp.exe would translate it into, and it says:

TlbImp : warning TI3015 : At least one of the arguments for 'Marshal2.IMarshal3.GetActiveNames' cannot be marshaled by the runtime marshaler. Such arguments will therefore be passed as a pointer and may require unsafe code to manipulate.

If you can, I suggest changing the function to return a SAFEARRAY containing BSTRs. At the C# side you would see a System.Array, whose elements (of type Object) can be cast to String.

I also suggest making GetActiveNames a method of a COM class. P/Invoke is too primitive and unsafe.

user1610015
  • 6,561
  • 2
  • 15
  • 18
  • Thanks for the suggestion. But, this function is in a 3rd party dll. So, I do not have control on the native function. If we have to play with the pointers at the C# end, how would we do that? – user1683000 Sep 19 '12 at 15:53
  • You'd declare it as `int GetActiveNames(IntPtr names)` or maybe `unsafe int GetActiveNames(byte** names)` and start playing with pointers. Or use C++/CLI as a bridge to call the unmanaged GetActiveNames and provide a proper API to the C# side. I'd recommend C++/CLI especially if the caller is responsible for deallocating the result, which may need to be done with the `delete` operator (which can't be used in C#). – user1610015 Sep 19 '12 at 16:59
  • I'm very new to this.. Could you pls provide some code snippets showing how to do that? – user1683000 Sep 19 '12 at 17:14
0

Using C# to P/Invoke the function:

using System.Runtime.InteropServices;

public class ActiveNames
{
    public string[] ActiveNames { get; set; }

    [DllImport("GetActiveNames.dll")] // replace with the actual name of the DLL
    unsafe private static int GetActiveNames(sbyte** names):

    public void GetActiveNames()
    {
        unsafe
        {
            // I use 100 here as an artificial number. This may not be a reasonable
            // assumption, but I don't know how the actual GetActiveNames works
            sbyte** names = (sbyte**)Marshal.AllocHGlobal(100).ToPointer();

            try
            {
                GetActiveNames(names);

                // fill the ActiveNames property
                ActiveNames = new string[100];

                for (int i = 0; i < 100; ++i)
                    ActiveNames[i] = new string(names[i]);
            }
            finally
            {
                // deallocate the unmanaged names memory
                Marshal.FreeHGlobal(IntPtr((void*)names));
            }
        }
    }
}

And using C++/CLI (no need for P/Invoke):

#include "GetActiveNames.h" // replace with the actual GetActiveNames's header

using namespace System;
using namespace System::Runtime::InteropServices;

public ref class ActiveNames
{
private:
    array<String^>^ m_activeNames;

public:
    property array<String^>^ ActiveNames
    {
        array<String^>^ get()
        {
            return m_activeNames;
        }

        void set(array<String^>^ names)
        {
            m_activeNames = names;
        }
    }

    void GetActiveNames()
    {
        signed char** names = new signed char*[100];

        try
        {
            ::GetActiveNames(reinterpret_cast<char**>(names));

            ActiveNames = gcnew array<String^>(100);

            for (int i = 0; i < 100; ++i)
                ActiveNames[i] = gcnew String(names[i]);
        catch (...)
        {
            delete[] names;
            throw;
        }

        delete[] names;
    }
};

As you can see, I made some unsafe assumptions because I don't know how GetActiveNames works (how is the memory for the names allocated and deallocated? how do you know how many names were returned?). You'll have to be more specific if you want more help.

user1610015
  • 6,561
  • 2
  • 15
  • 18
  • I tried the P/Invoke way and its showing the following error Attempted to read or write protected memory. This is often an indication that other memory is corrupt.. – user1683000 Sep 19 '12 at 20:30
0

Wow that really is a hideous API. Isn't there anything else even remotely... oh I don't know... DECENT, that you can use? But if you really want it...

using System;
using System.Runtime.InteropServices;

public class ActiveProjects
{
    public string[] ProjectPaths { get; set; }
    public ushort[] InstanceIds { get; set; }
    public string[] MachineNames { get; set; }
    public string[] ControllerIps { get; set; }
    public string[] ModelPaths { get; set; }
    public ushort[] NumberOfModelsPerProject { get; set; }
    public ushort[] ModelIds { get; set; }
    public ushort[] ModelStates { get; set; }

    [DllImport("<the DLL>")]
    unsafe private static int OpalGetActiveProjects(
        ushort allocatedProjects,
        ushort* numProjects,
        ushort allocatedModels,
        ushort* numModels,
        ushort allocatedProjectsPathLen,
        ushort* maxProjectPathLen,
        ushort allocatedModelPathLen,
        ushort* maxModelPathLen,
        ushort allocatedMachineNameLen,
        ushort* maxMachineNameLen,
        ushort allocatedIpLen,
        ushort* maxIpLen,
        sbyte** projectPaths,
        ushort* instanceIDs,
        sbyte** machineNames,
        sbyte** controllerIPs,
        sbyte** modelPaths,
        ushort* numModelsPerProject,
        ushort* modelIDs,
        ushort* modelStates
        );

    public void GetActiveProjects()
    {
        unsafe
        {
            ushort numberOfProjects = 0;
            ushort numberOfModels = 0;
            ushort maxProjectPathLength = 0;
            ushort maxModelPathLength = 0;
            ushort maxMachineNameLength = 0;
            ushort maxIpLength = 0;

            int result = OpalGetActiveProjects(
                0,
                &numberOfProjects,
                0,
                &numberOfModels,
                0,
                &maxProjectPathLength,
                0,
                &maxModelPathLength,
                0,
                &maxMachineNameLength,
                0,
                &maxIpLength,
                null,
                null,
                null,
                null,
                null,
                null,
                null,
                null
                );

            if (result != 0 && result != 123)
                throw new Exception("Error getting active projects");

            sbyte** projectPaths = null;
            ushort* instanceIds = null;
            sbyte** machineNames = null;
            sbyte** controllerIps = null;
            sbyte** modelPaths = null;
            ushort* numberOfModelsPerProject = null;
            ushort* modelIds = null;
            ushort* modelStates = null;

            try
            {
                projectPaths = (sbyte**)Marshal.AllocHGlobal(numberOfProjects * IntPtr.Size).ToPointer();
                for (int i = 0; i < numberOfProjects; ++i)
                    projectPaths[i] = null;
                for (int i = 0; i < numberOfProjects; ++i)
                    projectPaths[i] = (sbyte*)Marshal.AllocHGlobal(maxProjectPathLength);

                instanceIds = (ushort*)Marshal.AllocHGlobal(numberOfProjects * 2).ToPointer();

                machineNames = (sbyte**)Marshal.AllocHGlobal(numberOfProjects * IntPtr.Size).ToPointer();
                for (int i = 0; i < numberOfProjects; ++i)
                    machineNames[i] = null;
                for (int i = 0; i < numberOfProjects; ++i)
                    machineNames[i] = (sbyte*)Marshal.AllocHGlobal(maxMachineNameLength).ToPointer();

                controllerIps = (sbyte**)Marshal.AllocHGlobal(numberOfProjects * IntPtr.Size).ToPointer();
                for (int i = 0; i < numberOfProjects; ++i)
                    controllerIps[i] = null;
                for (int i = 0; i < numberOfProjects; ++i)
                    controllerIps[i] = (sbyte*)Marshal.AlloHGlobal(maxIpLength).ToPointer();

                modelPaths = (sbyte**)Marshal.AlloHGlobal(numberOfModels * IntPtr.Size).ToPointer();
                for (int i = 0; i < numberOfProjects; ++i)
                    modelPaths = null;
                for (int i = 0; i < numberOfProjects; ++i)
                    modelPaths = (sbyte*)Marshal.AllocHGlobal(maxModelPathLength).ToPointer();

                numberOfModelsPerProject = (ushort*)Marshal.AlloHGlobal(numberOfProjects * 2).ToPointer();
                modelIds = (ushort*)Marshal.AllocHGlobal(numberOfModels * 2).ToPointer();
                modelStates = (ushort*)Marshal.AllocHGlobal(numberOfModels * 2).ToPointer();

                ushort numberOfProjects2 = 0;
                ushort numberOfModels2 = 0;
                ushort maxProjectPathLength2 = 0;
                ushort maxModelPathLength2 = 0;
                ushort maxMachineNameLength2 = 0;
                ushort maxIpLength2 = 0;

                OpalGetActiveProjects(
                    numberOfProjects,
                    &numberOfProjects2,
                    numberOfModels,
                    &numberOfModels2,
                    maxProjectPathLength,
                    &maxProjectPathLength2,
                    maxModelPathLength,
                    &maxModelPathLength2,
                    maxMachineNameLength,
                    &maxMachineNameLength2,
                    maxIpLength,
                    &maxIpLength2,
                    projectPaths,
                    instanceIds,
                    machineNames,
                    controllerIps,
                    modelPaths,
                    numberOfModelsPerProject,
                    modelIds,
                    modelStates
                    );

                ProjectPaths = new string[numberOfProjects2];
                for (int i = 0; i < numberOfProjects2; ++i)
                    ProjectPaths[i] = new string(projectPaths[i]);

                InstanceIds = new ushort[numberOfProjects2];
                for (int i = 0; i < numberOfProjects2; ++i)
                    InstanceIds[i] = instanceIds[i];

                MachineNames = new string[numberOfProjects2];
                for (int i = 0; i < numberOfProjects2; ++i)
                    MachineNames[i] = new string(machineNames[i]);

                ControllerIps = new string[numberOfProjects2];
                for (int i = 0; i < numberOfProjects2; ++i)
                    ControllerIps[i] = new string(controllerIps[i]);

                ModelPaths = new string[numberOfModels2];
                for (int i = 0; i < numberOfModels2; ++i)
                    ModelPaths[i] = new string(modelPaths[i]);

                NumberOfModelsPerProject = new ushort[numberOfProjects2];
                for (int i = 0; i < numberOfProjects2; ++i)
                    NumberOfModelsPerProject[i] = numberOfModelsPerProject[i];

                ModelIds = new ushort[numberOfModels2];
                for (int i = 0; i < numberOfModels2; ++i)
                    ModelIds[i] = modelIds[i];

                ModelStates = new ushort[numberOfModels2];
                for (int i = 0; i < numberOfModels2; ++i)
                    ModelStates[i] = modelStates[i];
            }
            finally
            {
                if (projectPaths != null)
                {
                    for (int i = 0; i < numberOfProjects && projectPaths[i] != null; ++i)
                        Marshal.FreeHGlobal(IntPtr((void*)projectPaths[i]));

                    Marshal.FreeHGlobal(IntPtr((void*)projectPaths));
                }

                if (instanceIds != null)
                    Marshal.FreeHGlobal(IntPtr((void*)instanceIds));

                if (machineNames != null)
                {
                    for (int i = 0; i < numberOfProjects && machineNames[i] != null; ++i)
                        Marshal.FreeHGlobal(IntPtr((void*)machineNames[i]));

                    Marshal.FreeHGlobal(IntPtr((void*)machineIds));
                }

                if (controllerIps != null)
                {
                    for (int i = 0; i < numberOfProjects && controllerIps[i] != null; ++i)
                        Marshal.FreeHGlobal(IntPtr((void*)controllerIps[i]));

                    Marshal.FreeHGlobal(IntPtr((void*)controllerIps));
                }

                if (modelPaths != null)
                {
                    for (int i = 0; i < numberOfModels && modelPaths[i] != null; ++i)
                        Marshal.FreeHGlobal(IntPtr((void*)modelPaths[i]));

                    Marshal.FreeHGlobal(IntPtr((void*)modelPaths));
                }

                if (numberOfModelsPerProject != null)
                    Marshal.FreeHGlobal(IntPtr((void*)numberOfModelsPerProject));

                if (modelIds != null)
                    Marshal.FreeHGlobal(IntPtr((void*)modelIds));

                if (modelStates != null)
                    Marshal.FreeHGlobal(IntPtr((void*)modelStates));
            }
        }
    }
}
user1610015
  • 6,561
  • 2
  • 15
  • 18