Home | Contact Us | FAQ | Search & Site Map | Link to Us
Sign In | Join | Other 45 Sites in Network
HomeAnnouncementsFree MagazinesWhite PapersSubmit Content
Discussion GroupsASP.NETWindows FormsLanguages.NET FrameworkVisual Studio.NET
Articles.NET FrameworkASP.NETToolsWindows Forms
.NET DirectoryOpen Source ProjectsUser GroupsWeb Resources
Related Topics
Visual Basic 6SQL ServerMS AccessOther DB ProductsMS Server ProductsMore Topics ...

.NET Forum / .NET Framework / CLR / May 2008

Tip: Looking for answers? Try searching our database.

am having a problem with pinvoke and StringBuilder[ ]

Thread view: 
Enable EMail Alerts  Start New Thread
Thread rating: 
LK - 29 Apr 2008 16:52 GMT
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
Willy Denoyette [MVP] - 29 Apr 2008 17:58 GMT
> Hi,
>
[quoted text clipped - 85 lines]
> 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.
LK - 30 Apr 2008 05:56 GMT
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

>> Hi,
>>
[quoted text clipped - 107 lines]
>
> Willy.
Willy Denoyette [MVP] - 30 Apr 2008 21:49 GMT
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.

> Hi Willy,
>
[quoted text clipped - 136 lines]
>>
>> Willy.
Pavel Minaev - 30 Apr 2008 20:43 GMT
> 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
[quoted text clipped - 6 lines]
> NullPointerExceptions occured and all messages populated in
> the String[] in managed code was correctly printed by the DLL).

Note that String is only immutable from within .NET (and even then
only if you don't access it via pointers), and immutability is not
enforced in unmanaged code. If you pass a String to a function that'll
write something into it, it'll work just fine - just be sure to create
a new instance of string (e.g. via "new string('\0', capacity)")
specifically for this purpose, so that you hold the only managed
reference to it.

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

You are doing it logically, but unfortunately, P/Invoke only supports
StringBuilders as top-level function arguments - arrays of
StringBuilder, or fields of type StringBuilder inside marshalled
structs, are not supported (you get a meaningful exception for the
latter telling you as much, at least, but the former just passes some
weird data to the function called).

Therefore, you have three options: either use plain strings as
described above, use IntPtr and marshal stuff manually using the
Marshal.PtrToStringAnsi and Marshal.StringToHGlobalAnsi as needed, or
use plain unsafe pointers (byte*** in your case) and handle
conversions & allocations manually as you see fit (e.g. stackalloc /
Encoding.GetBytes / Encoding.GetChars).
Ben Voigt [C++ MVP] - 01 May 2008 21:19 GMT
> 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.

Might I suggest a C++/CLI wrapper class?

> Since the String class is immutable, I have used StringBuilder in my code
> but this results in a NullPointerException. Just for testing, I replaced
[quoted text clipped - 78 lines]
> csc CallDll.cs
> CallDll
Willy Denoyette [MVP] - 01 May 2008 22:18 GMT
>> Hi,
>>
[quoted text clipped - 88 lines]
>> csc CallDll.cs
>> CallDll

Free Magazines

Get these publications absolutely FREE for up to 12 months. There are no hidden fees and no obligation. Simply choose a title, complete the application form and submit it. Read more ...

Oracle MagazineNetwork ComputingComputer WorldBio-IT WorldeWeekInformation WeekInfosecurity
 
Sign In
Join
My Latest Posts
My Monitored Threads
My Blog
My Photo Gallery
My Profile
My Homepage

Start New Thread
Enable EMail Alerts
Rate this Thread



©2008 Advenet LLC   Privacy Policy - Terms of Use
This website includes both content owned or controlled by Advenet as well as content owned or controlled by third parties.