Solved

Pass string from C# dll to C expecting LPStr

Posted on 2008-10-17
4
3,485 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

Salesforce Has Never Been Easier

Improve and reinforce salesforce training & adoption using WalkMe's digital adoption platform. Start saving on costly employee training by creating fast intuitive Walk-Thrus for Salesforce. Claim your Free Account Now

Question has a verified solution.

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

Suggested Solutions

Summary: This tutorial covers some basics of pointer, pointer arithmetic and function pointer. What is a pointer: A pointer is a variable which holds an address. This address might be address of another variable/address of devices/address of fu…
Performance in games development is paramount: every microsecond counts to be able to do everything in less than 33ms (aiming at 16ms). C# foreach statement is one of the worst performance killers, and here I explain why.
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.
The viewer will learn how to user default arguments when defining functions. This method of defining functions will be contrasted with the non-default-argument of defining functions.

734 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