Jonneh
Jonneh

Reputation: 245

Delphi Unicode and Console

I am writing a C# application that interfaces with a pair of hardware sensors. Unfortunately the only interface that is exposed on the devices requires a provided dll written in Delphi.

I am writing a Delphi executable wrapper that takes calls the necessary functions for the DLL and returns the sensor data over stout. However, the return type of this data is a PWideChar (or PChar) and I have been unable to convert it to ansi for printing on command line.

If I directly pass the data to WriteLn, I get '?' for each character. If I look through the array of characters and attempt to print them one at a time with an Ansi Conversion, only a few of the characters print (they do confirm the data though) and they will often print out of order. (printing with the index exposed simply jumps around.) I also tried converting the PWideChar's to integer straight: 'I' corresponds to 21321. I could potentially figure out all the conversions, but some of the data has a multitude of values.

I am unsure of what version of Delphi the dll uses, but I believe it is 4. Definately prior to 7.

Any help is appreciated!

TLDR: Need to convert UTF-16 PWideChar to AnsiString for printing.

Example application:

program SensorReadout;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  Windows,
  SysUtils,
  dllFuncUnit in 'dllFuncUnit.pas'; //This is my dll interface.

var  state: integer;
     answer: PChar;
     I: integer;
     J: integer;
     output: string;
     ch: char;

begin
  try
    for I := 0 to 9 do
    begin
      answer:= GetDeviceChannelInfo_HSI(1, Ord('a'), I, state); //DLL Function, returns a PChar with the results.  See example in comments.

      if state = HSI_NO_ERRORCODE then
      begin
        output:= '';
        for J := 0 to length(answer) do
        begin
          ch:= answer[J]; //This copies the char. Originally had an AnsiChar convert here.
          output:= output + ch;
        end;
        WriteLn(output);
      end;

  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
  ReadLn(I);
end.`

The issue was PAnsiChar needed to be the return type of the function sourced from the DLL.

Upvotes: 2

Views: 1749

Answers (4)

Boris Matkov
Boris Matkov

Reputation: 1

To convert UTF-16 PWideChar to AnsiString you can use simple cast:

var
  WStr: WideString;
  pWStr: PWideString;
  AStr: AnsiString;
begin
  WStr := 'test';
  pWStr := PWideString(WStr);
  AStr := AnsiString(WideString(pWStr));
end;

Upvotes: 0

MajidTaheri
MajidTaheri

Reputation: 3983

type of answer(variable) is PChar. use length function good for string variable. use strlen instead of length.

 for J := 0 to StrLen(answer)-1 do

also accessible range of PChar(char *) is 0..n-1

Upvotes: 0

David Heffernan
David Heffernan

Reputation: 612794

To convert PWideChar to AnsiString:

function WideCharToAnsiString(P: PWideChar): AnsiString;
begin
  Result := P;
end;

The code converts from UTF-16, null-terminated PWideChar to AnsiString. If you are getting question marks in the output then either your input is not UTF-16, or it contains characters that cannot be encoded in your ANSI codepage.

My guess is that what is actually happening is that your Delphi DLL was created with a pre-Unicode Delphi and so uses ANSI text. But now you are trying to link to it from a post-Unicode Delphi where PChar has a different meaning. I'm sure Rob explained this to you in your other question. So you can simply fix it by declaring your DLL import to return PAnsiChar rather than PChar. Like this:

function GetDeviceChannelInfo_HSI(PortNumber, Address, ChNumber: Integer;
  var State: Integer): PAnsiChar; stdcall; external DLL_FILENAME;

And when you have done this you can assign to a string variable in a similar vein as I describe above.

What you need to absorb is that in older versions of Delphi, PChar is an alias for PAnsiChar. In modern Delphi it is an alias for PWideChar. That mismatch would explain everything that you report.


It does occur to me that writing a Delphi wrapper to the DLL and communicating via stdout with your C# app is a very roundabout approach. I'd just p/invoke the DLL directly from the C# code. You seem to think that this is not possible, but it is quite simple.

[DllImport(@"mydll.dll")]
static extern IntPtr GetDeviceChannelInfo_HSI(
    int PortNumber, 
    int Address, 
    int ChNumber,
    ref int State
);

Call the function like this:

IntPtr ptr = GetDeviceChannelInfo_HSI(Port, Addr, Channel, ref State);

If the function returns a UTF-16 string (which seems doubtful) then you can convert the IntPtr like this:

string str = Marshal.PtrToStringUni(ptr);

Or if it is actually an ANSI string which seems quite likely to me then you do it like this:

string str = Marshal.PtrToStringAnsi(ptr);

And then of course you'll want to call into your DLL to deallocate the string pointer that was returned to you, assuming it was allocated on the heap.

Upvotes: 2

Despatcher
Despatcher

Reputation: 1725

Changed my mind on the comment - I'll make it an answer:)

According to that code if "state" is a code <> HSI_NO_ERRORCODE and there is no exception then it will write the uninitialised string "output" to the console. Which could be anything including accidentally showing "S" and "4" and a series of 1 or more question marks

Upvotes: 0

Related Questions