Solved

Pass string from C# dll to C expecting LPStr

Posted on 2008-10-17
4
3,496 Views
Last Modified: 2012-06-22
I'm trying to write a passive integration dll.
The application expects a win32 dll which implements particular methods, these methods should be "declared to use the WINAPI calling convention (__stdcall in Visual C)" apparently.

As an experiment I'm trying to write this dll in C# (I don't know C or C++) and I'm disassembling it with ildasm manually editing the file to expose the methods and then reassembling it with ilasm as per this site http://blong.com/Conferences/BorConUK2002/Interop1/Win32AndDotNetInterop.htm#ExportingMethods. This bit actually seems to be working fine and the application is certainly picking up the dll and can get the correct results from a method which just returns an int (C# int being the equivalent of C long).
Where I'm running into trouble is when I'm trying to implement a method which should have the following interface according to the application documentation
long GetFieldName(LPSTR fieldName, long fieldNum)
Now I can return the correct long value and I'm receiving the expected fieldNum value correctly but the method should then write a value to the fieldName parameter and the application then picks up this value.
What I'm getting out of this at the moment is gibberish.
The documentation says this about the fieldName parameter

LPSTR fieldName [out]: The buffer for the field name. It can hold up to a maximum of 50 characters plus a NULL terminator.


I've attached the current state of the code for this method.
As you can see I'm trying to use MarshalAs(UnmanagedType.LPStr) with a StringBuilder which is passed out. I have also tried this with LPWStr, a string, without the out keyword and without Marshaling.
I haven't tried it as a ref as I didn't think this would be valid, is this the wrong assumption?

I don't have access to change the application code, only the C# dll.
Any suggestions are appreciated, except those to just write it in C or C++.

Thanks,
Colette
public static int GetFieldName([MarshalAs(UnmanagedType.LPStr)]out StringBuilder fieldName, int fieldNum)
{
    LogMessage("In GetFieldName, fieldNum is " + fieldNum.ToString(), EventLogEntryType.Information);
    int retVal = 0;
    fieldName = new StringBuilder("", 49);
 
    try{
       if (fieldNum == 0){
          fieldName.Append("Hello");
          retVal = 1;
       }
       else if (fieldNum == 1){
          fieldName.Append("Goodbye");
          retVal = 1;
       }
       else{
          retVal = 0;
       }
   }
   catch (Exception ex){
       LogMessage(ex.Message + Environment.NewLine + ex.StackTrace, EventLogEntryType.Error);
   }
return retVal;
}

Open in new window

0
Comment
Question by:capsoftuk
[X]
Welcome to Experts Exchange

Add your voice to the tech community where 5M+ people just like you are talking about what matters.

  • Help others & share knowledge
  • Earn cash & points
  • Learn & ask questions
  • 2
4 Comments
 
LVL 86

Expert Comment

by:jkr
ID: 22741836
http://msdn.microsoft.com/msdnmag/issues/02/08/CQA/ pretty much targets this issue. E.g.

C/C++ DLL ("returnstring.dll"):  

#include <windows.h>
#include <tchar.h>

int GetString(LPSTR buf, int nMaxCount)
{
      TCHAR* pszText = _T( "Hi from unmanaged code");
      int nLen = _tcslen(pszText);

      if (nLen < nMaxCount)
      {
         _tcscpy(buf,pszText);

         return nLen;
      }

      return 0;
}

C#

using System.Text; // for StringBuilder
public class ReturnStringDll{
  [DllImport("returnstring.dll")]
  public static extern int GetString(StringBuilder buf, int nMaxCount);
}

StringBuilder cb = new StringBuilder(256);
ReturnStringDll.GetString(sb, sb.Capacity);

That works for just displaying a string on the C/C++ side also.
0
 

Author Comment

by:capsoftuk
ID: 22742072
Would that it were that simple jkr :)
I haven't explained myself very well.
I'm not calling the C methods, I don't have access to them at all.
This application that I'm working with lets you write dlls to expand it's functionality.
One of the types it supports requires you to create a dll that implements the method I outlined in my first post GetFieldName and then I just need to register the dll with the application so it can see it.
It's the application that's calling the C# dll, not the other way around.

So the C application is passing the LPSTR parameter into the C# dll method and I don't know how to manipulate the parameter.
0
 

Accepted Solution

by:
capsoftuk earned 0 total points
ID: 22742414
I've sorted it.
I ditched the ideal the LPSTR was a string and could be handled by a StringBuilder or string and went with the idea that it's just a pointer.
I've included my updated code.
Basically I've set the expected parameter to be an IntPtr and then copied an array of bytes to it using Marshal.Copy.
Works like a charm and now I've only got another 2 even more complicated such scenarios to work through.
public unsafe static int GetFieldName(IntPtr fieldName, int fieldNum)
{
   int retVal = 0;
   string fName = "";
   try{
      if (fieldNum == 0){
         fName = "Hello";
         retVal = 1;
      }
      else if (fieldNum == 1){
         fName = "Goodbye";
         retVal = 1;
      }
      else{
         retVal = 0;
      }
      byte[] ba = Encoding.ASCII.GetBytes(fName);
      Marshal.Copy(ba, 0, fieldName, ba.Length);
      
   }
   catch (Exception ex){
      LogMessage(ex.Message + Environment.NewLine + ex.StackTrace, EventLogEntryType.Error);
   }
   return retVal;
}

Open in new window

0
 
LVL 39

Expert Comment

by:itsmeandnobodyelse
ID: 22748109
>>>> I ditched the ideal the LPSTR was a string and could be handled by a
>>>> StringBuilder or string and went with the idea that it's just a pointer.

A LPSTR is typedef of char* what is a (writeable) pointer to a char or (the first element of) a char array. If the char array has any contents, then it is  a zero-terminated string (chance>99%) which indeed could be handled by any string class of any language.

What could make the "gibbering" is that the char sequence is ANSI and the C# StringBuilder most probably has UNICODE strings. But the main reason why your first approach seem to be unsuccessful is that  were clearing the passed fieldName by

  fieldName = new StringBuilder("", 49);

while in the second approach you were marshaling the given char sequence to a byte array - what succeeds as a 'byte' is a unsigned char what is bit-identically with a (signed) char.



0

Featured Post

Free Tool: ZipGrep

ZipGrep is a utility that can list and search zip (.war, .ear, .jar, etc) archives for text patterns, without the need to extract the archive's contents.

One of a set of tools we're offering as a way to say thank you for being a part of the community.

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

Article by: SunnyDark
This article's goal is to present you with an easy to use XML wrapper for C++ and also present some interesting techniques that you might use with MS C++. The reason I built this class is to ease the pain of using XML files with C++, since there is…
This is a short and sweet, but (hopefully) to the point article. There seems to be some fundamental misunderstanding about the function prototype for the "main" function in C and C++, more specifically what type this function should return. I see so…
The goal of this video is to provide viewers with basic examples to understand how to use strings and some functions related to them in the C programming language.
Video by: Grant
The goal of this video is to provide viewers with basic examples to understand and use nested-loops in the C programming language.

695 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question