3

I want to send a C# structure with string array to a C++ function which accepts void * for the c# structure and char** for the c# structure string array member.

I was able to send the structure to c++ function,but the issue is , not able to access the string array data member of c# structure from c++ function. When sending the string array separately,i was able to access the array elements.

Sample code is-

C# Code:

[StructLayout(LayoutKind.Sequential)]
public struct TestInfo
{
    public int TestId;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
    public String[] Parameters;
}

[DllImport("TestAPI.dll", CallingConvention = CallingConvention.StdCall, EntryPoint    "TestAPI")]
private static extern void TestAPI(ref TestInfo data);

static unsafe void Main(string[] args)
{
TestInfo  testinfoObj = new TestInfo();
testinfoObj.TestId = 1;
List<string> names = new List<string>();
names.Add("first");
names.Add("second");
names.Add("third");
testinfoObj.Parameters=names.ToArray();
TestAPI(ref testinfoObj);
}



VC++ Code:

/*Structure with details for TestInfo*/
typedef struct TestInfo
{
int  TestId;
char **Parameters;
}TestInfo_t;

//c++ function
__declspec(dllexport) int TestAPI(void *data)
{
TestInfo *cmd_data_ptr= NULL;
cmd_data_ptr = (TestInfo) data;
printf("ID is %d \r\n",cmd_data_ptr->TestId);//Working fine

for(i = 0; i < 3; i++)
printf("value: %s \r\n",((char *)cmd_data_ptr->Parameters)[i]);/*Error-Additional     information: Attempted to read or write protected memory. This is often an indication that other memory is corrupt*/
}

When analyzing the memory stack, it is observed that,when i print ((char *)cmd_data_ptr->Parameters), the first array element("first") is getting printed, but using ((char *)cmd_data_ptr->Parameters)[i], not able access elements and above mentioned exception is coming.

The structure memory address contains address of all the structure elements,but while accessing the data from c++,it is accessing only the first element of the string array.

2 Answers 2

3
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
public String[] Parameters;

is an inline array. The C++ declaration that matches is:

char* Parameters[2];

But you are trying to match it to:

char** Parameters;

and that's completely different.

You will need to marshal this by hand. In the C# struct declare Parameters to be IntPtr. Then allocate native memory with Marshal.AllocHGlobal to hold an array of pointers. And then populate those pointers with pointers to your strings.

[StructLayout(LayoutKind.Sequential)]
public struct TestInfo
{
    public int TestId;
    public IntPtr Parameters;
}

static void Main(string[] args) // no need for unsafe
{
    TestInfo testInfo;
    testInfo.TestId = 1;
    testInfo.Parameters = Marshal.AllocHGlobal(2*Marshal.SizeOf(typeof(IntPtr)));
    IntPtr ptr = testInfo.Parameters;
    Marshal.WriteIntPtr(ptr, Marshal.StringToHGlobalAnsi("foo"));
    ptr += Marshal.SizeOf(typeof(IntPtr));
    Marshal.WriteIntPtr(ptr, Marshal.StringToHGlobalAnsi("bar"));
    TestAPI(ref testinfoObj);
    // now you want to call FreeHGlobal, I'll leave that code to you
}

An alternative would be to use a pinned IntPtr[] and put that in testInfo.Parameters.

Sign up to request clarification or add additional context in comments.

Comments

2

This is really more of an expansion/extension to David's answer, but here's one way to wrap up the custom marshalling:

public struct LocalTestInfo
{
    public int TestId;
    public IEnumerable<string> Parameters;

    public static explicit operator TestInfo(LocalTestInfo info)
    {
        var marshalled = new TestInfo
            {
                TestId = info.TestId, 
            };
        var paramsArray = info.Parameters
            .Select(Marshal.StringToHGlobalAnsi)
            .ToArray();
        marshalled.pinnedHandle = GCHandle.Alloc(
            paramsArray, 
            GCHandleType.Pinned);
        marshalled.Parameters = 
            marshalled.pinnedHandle.AddrOfPinnedObject();
        return marshalled;
    }
}

[StructLayout(LayoutKind.Sequential)]
public struct TestInfo : IDisposable
{
    public int TestId;
    public IntPtr Parameters;

    [NonSerialized]
    public GCHandle pinnedHandle;

    public void Dispose()
    {
        if (pinnedHandle.IsAllocated)
        {
            Console.WriteLine("Freeing pinned handle");
            var paramsArray = (IntPtr[])this.pinnedHandle.Target;
            foreach (IntPtr ptr in paramsArray)
            {
                Console.WriteLine("Freeing @ " + ptr);
                Marshal.FreeHGlobal(ptr);
            }
            pinnedHandle.Free();
        }
    }
}

Note for my test I swapped over to CDecl:

[DllImport(@"Test.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int TestAPI(ref TestInfo info);

Also I think you had a typo in the C++ side:

extern "C" 
__declspec(dllexport) int TestAPI(void *data)
{
    TestInfo *cmd_data_ptr= NULL;
    cmd_data_ptr = (TestInfo*) data;
    printf("ID is %d \r\n",cmd_data_ptr->TestId);

    // char**, not char*
    char** paramsArray = ((char **)cmd_data_ptr->Parameters);
    for(int i = 0; i < 3; i++)
    {
        printf("value: %s \r\n",paramsArray[i]);
    }
    return 0;
}

And a test rig:

static void Main(string[] args)
{
    var localInfo = new LocalTestInfo()
    {
        TestId = 1,
        Parameters = new[]
        {
            "Foo", 
            "Bar",
            "Baz"
        }
    };
    TestInfo forMarshalling;
    using (forMarshalling = (TestInfo)localInfo)
    {
        TestAPI(ref forMarshalling);                
    }
}

The reverse marshalling operator is left as an exercise to the reader, but should basically look like the inverse of the explicit TestInfo operator.

1 Comment

@DavidHeffernan Meh, it's a mess, but thank'ye nonetheless - I've been bitten numerous times for not cleaning up after myself wrt Pinvokes, so I've got a number of go-to patterns for automagical cleanup.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.