Re: am having a problem with pinvoke and StringBuilder[ ]

Tech-Archive recommends: Repair Windows Errors & Optimize Windows Performance



If you don't have the source of the DLL then you are in a world of pain, that means you wont be able to use the interop marshaler, you need to "custom" marshal.
The function takes a char*** boards, that means boards is a pointer to a pointer to a pointer to a char.
And the function returns a count of boards, so my guess is that the final pointer points to buffer of zero terminated char arrays, like this:

VGA\0NIC\0....
What you need to do is pass a ref to an IntPtr that points to a buffer that holds an IntPtr pointing to a buffer.
On return you know by means of the returned boards count how many zero terminated strings you have in the buffer.

Following is a working sample illustrating the process.

// C# test.cs
using System;
using System.Text;
using System.Runtime.InteropServices;

class CallDll
{
[DllImport("TestLib.dll", CharSet = CharSet.Ansi)]
public static extern int EnumerateBoards(ref int numBoards, ref IntPtr ptr);

public static void Main()
{
int numBoards = 0;
int maxBoards = 5;

IntPtr boards = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(IntPtr)));
IntPtr ptr = Marshal.AllocHGlobal(100); // allocate buffer in unmanaged large enough to hold all strings returned
Marshal.WriteIntPtr(boards, ptr);
EnumerateBoards(ref numBoards, ref boards);
Console.WriteLine("Found " + numBoards + " boards");
string[] sa = new string[numBoards];
for(int i = 0; i < numBoards; i++)
{
sa[i] = Marshal.PtrToStringAnsi(ptr);
Console.WriteLine("{0}", sa[i]);
ptr = new IntPtr(ptr.ToInt32() + sa[i].Length + 1); // new offset including string terminator
}
}
}


// C++ file testlib.cpp
#include <string.h>

extern "C" __declspec(dllexport) int __stdcall EnumerateBoards(int *numBoards, char ***boards)
{
strcpy_s(static_cast<char*>(**boards), 4, "VGA\0");
strcpy_s(static_cast<char*>(**boards + 4), 4, "NIC\0");
strcpy_s(static_cast<char*>(**boards + 8), 6, "SOUND\0");
*numBoards = 3;
return 0;
}


Willy.


"LK" <lkant2000@xxxxxxxxx> wrote in message news:Oz%23dN6nqIHA.1768@xxxxxxxxxxxxxxxxxxxxxxx
Hi Willy,

thanks a lot for your feedback.

1) I tried out your suggestions and that did not work either.
Although I dont get NullPointerExceptions, no data gets
passed between managed and unmanaged code. Here is
the output from my modified code.

c# b4: board0: 1111111111111111111
c# b4: board1: 2222222222222222222

DLL: Hello from TestLib.dll
DLL: board0: +4ù (prints junk instead of 1111111....)
DLL: board1: +4ù (prints junk instead of 2222222....)

c# Found 2 boards

c# after: board0: 1111111111111111111 (should actually print VGA )
c# after: board1: 2222222222222222222 (should actually print NIC)

2) Unfortunately, I dont have the source code for the thirdparty DLL and
it does take a char *** argument. Is there any way that I can pass a
char *** argument from C# to a DLL.

again, thanks for your help.
LK

"Willy Denoyette [MVP]" <willy.denoyette@xxxxxxxxxx> wrote in message news:uulFLphqIHA.4928@xxxxxxxxxxxxxxxxxxxxxxx
"LK" <lkant2000@xxxxxxxxx> wrote in message news:u0RgHEhqIHA.524@xxxxxxxxxxxxxxxxxxxxxxx
Hi,

From a C# program I need to access an existing DLL that enumerates the
boards detected via a hardware probe of the system. The function that
does the enumeration expects an int * where it stores the number of boards
detected and a char *** where it stores the name of each board detected.

Since the String class is immutable, I have used StringBuilder in my code
but this results in a NullPointerException. Just for testing, I replaced
StriingBuilder[] with String[] and the code worked just fine (i.e, no
NullPointerExceptions occured and all messages populated in
the String[] in managed code was correctly printed by the DLL).

I am wondering what I am doing wrong in my code. For reference, I am
enclosing my C# code and my sample DLL code

thank you for your help.
Laxmikant Rashinkar (LK)

Here is the C# code
---------------------
using System;
using System.Text;
using System.Runtime.InteropServices;

class CallDll
{
[DllImport("TestLib.dll")]
public static extern void AccessCheck();

[DllImport("TestLib.dll")]
public static extern int EnumerateBoards(ref int numBoards, ref StringBuilder[] boards);

public static void Main()
{
int numBoards = 0;
int maxBoards = 5;
int i;

StringBuilder[] boards = new StringBuilder[maxBoards];
for(i=0; i<maxBoards; i++)
boards[i] = new StringBuilder(100);

AccessCheck();
EnumerateBoards(ref numBoards, ref boards);
Console.WriteLine("Found " + numBoards + " boards\n");
for(i=0; i<numBoards; i++)
Console.WriteLine("" + boards[i] + "\n");
}
}

Here is the DLL code
----------------------
#include <stdio.h>
#include <string.h>

extern "C" __declspec(dllexport) void AccessCheck()
{
printf("Hello from TestLib.dll\n");
}

extern "C" __declspec(dllexport) int EnumerateBoards(int *numBoards, char ***boards)
{
char **cpp = *boards;

strcpy(cpp[0], "VGA");
strcpy(cpp[1], "NIC");
*numBoards = 2;
return 0;
}

Here is the output from my program:
------------------------------------
Hello from TestLib.dll

Unhandled Exception: System.NullReferenceException: Object reference not set
to an instance of an object.
at CallDll.EnumerateBoards(Int32& numBoards, StringBuilder[]& boards)
at CallDll.Main()

And here is how I compile my test code
----------------------------------------
@echo off
cl TestLib.cpp -LD -FeTestLib.dll
csc CallDll.cs
CallDll




Why the triple (***) indirection, a stringBuilder is passed as a pointer and a StringBuilder[] is passed as a pointer to a pointer?

..
extern "C" __declspec(dllexport) int EnumerateBoards(int *numBoards, char **boards)
{
char **cpp = boards;
..

Remove the ref from the DllImport declaration and the function call in your C# code and it should work.

...
public static extern int EnumerateBoards(ref int numBoards, StringBuilder[] boards);

EnumerateBoards(ref numBoards, boards);


Willy.






.



Relevant Pages

  • Re: am having a problem with pinvoke and StringBuilder[ ]
    ... DLL: Hello from TestLib.dll ... Since the String class is immutable, I have used StringBuilder in my code ... public static extern int EnumerateBoards(ref int numBoards, ...
    (microsoft.public.dotnet.framework.clr)
  • Re: pinvoke with StringBuilder[] not working for me
    ... The function that does the enumeration expects an int * ... Just for testing I replaced StriingBuilderwith String[] and the code ... the Stringin managed code was correctly printed by the DLL). ... public static extern int EnumerateBoards(ref int numBoards, ...
    (microsoft.public.dotnet.framework.interop)
  • Re: am having a problem with pinvoke and StringBuilder[ ]
    ... Since the String class is immutable, I have used StringBuilder in my code ... the Stringin managed code was correctly printed by the DLL). ... public static extern int EnumerateBoards(ref int numBoards, ...
    (microsoft.public.dotnet.framework.clr)
  • Re: am having a problem with pinvoke and StringBuilder[ ]
    ... From a C# program I need to access an existing DLL that enumerates the ... the Stringin managed code was correctly printed by the DLL). ... public static extern int EnumerateBoards(ref int numBoards, ...
    (microsoft.public.dotnet.framework.clr)
  • Re: pinvoke with StringBuilder[] not working for me
    ... public static extern int EnumerateBoards ... int numBoards, StringBuilder boards); ...
    (microsoft.public.dotnet.framework.interop)