3

I have a native Delphi exe which calls into C# dll via COM interop. Here's the simplest case which demonstrate this issue:

Delphi:

IClass1 = interface(IDispatch)
  ['{B29BAF13-E9E4-33D7-9C92-FE28416C662D}']
  function Test(const aStr: WideString): WideString; safecall;
end;

var
  obj: IClass1;
  s: string;
begin
  obj := CoClass1.Create as IClass1;
  s := obj.Test('');  // Returns '[null]'
end;

C#:

[ComVisible(true)]
public interface IClass1
{
    string Test(string aStr);
}

[ComVisible(true)]
public class Class1 : IClass1
{
    public string Test(string aStr)
    {
        if (aStr == null) return "[null]";
        if (aStr == "") return "[empty]";
        return "Not empty: " + aStr;
    }
}

When I call method Test with an empty string in Delphi, the C# part receives null as a parameter value. Why is that? Shouldn't it be an empty string also?

Max
  • 19,654
  • 13
  • 84
  • 122

4 Answers4

4

In Delphi, AnsiString, UnicodeString, and WideString values are represented by a nil pointer when they are empty. COM uses BSTR for strings. Delphi wraps BSTR with WideString. So there is no way to pass an "empty" non-nil string to a COM method that takes a WideString as a parameter, it will be nil instead.

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
3

In Delphi, a null (i.e., nil) and empty string are treated as equivalent. As such, passing '' for a string (or WideString) parameter passes nil internally -

program Project1;

{$APPTYPE CONSOLE}

{$R *.res}

procedure Foo(const S: WideString);
begin
  WriteLn(Pointer(S) = nil);
end;

begin
  Foo('Something');  //FALSE
  Foo('');  //TRUE
  ReadLn;
end.

The equation of null and empty strings was in fact copied from COM... so a COM library isn't really the place to insist on a C#-style distinction between the two.

Chris Rolliston
  • 4,788
  • 1
  • 16
  • 20
  • 1
    COM does make a distinction. A `BSTR` can be NULL or a 0-length non-NULL. It is Delphi that does not make a distinction. – Remy Lebeau Feb 24 '14 at 00:42
  • 1
    @RemyLebeau - for sure it can technically be given many COM conventions do not get compiler support in (say) C++. However, code is (or at least was) supposed to treat a null and zero-length `BSTR` as equivalent (see http://blogs.msdn.com/b/ericlippert/archive/2003/09/12/52976.aspx) – Chris Rolliston Feb 24 '14 at 13:25
2

Why does passing '' to a WideString parameter result in the other side receiving null? Well, that's just how Delphi represents an empty COM BSTR. If you really need to pass an empty string, you need to change IClass1 in the Delphi code to pass TBStr instead of WideString and use SysAllocString or SysAllocStringLen to create an empty TBStr.

Change the declaration of the function in the Delphi code to:

function Test(const aStr: TBStr): WideString; safecall;

And pass SysAllocStringLen('', 0) when you need to pass an empty string.

Here is a complete demonstration:

C#

using System;
using System.Runtime.InteropServices;

namespace ConsoleApplication1
{
    [ComVisible(true)]
    public interface IClass1
    {
        string Test(string aStr);
    }

    [ComVisible(true)]
    public class Class1 : IClass1
    {
        public string Test(string aStr)
        {
            if (aStr == null) return "[null]";
            if (aStr == "") return "[empty]";
            return "Not empty: " + aStr;
        }
    }

    class Program
    {
        [DllImport(@"Project1.dll")]
        static extern void Foo(IClass1 intf);

        static void Main(string[] args)
        {
            IClass1 intf = new Class1();
            Foo(intf);
        }
    }
}

Delphi

uses
  Ole2;

type
  IClass1 = interface(System.IDispatch)
    function Test(const aStr: TBStr): WideString; safecall;
  end;

var
  EmptyBStr: TBStr;

procedure Foo(const intf: IClass1); stdcall;
begin
  Writeln(intf.Test(nil));
  Writeln(intf.Test(EmptyBStr));
  Writeln(intf.Test(SysAllocString('foo')));
end;

exports
  Foo;

begin
  EmptyBStr := SysAllocStringLen('', 0);
end.

Output

[null]
[empty]
Not empty: foo
David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • I have updated my question with interface declaration. I didn't add it in the first place, because it was auto generated from the type library. – Max Feb 24 '14 at 09:27
  • @Max Thanks. My latest update shows you how to pass an empty string. – David Heffernan Feb 24 '14 at 09:37
0

To avoid errors by null pointers you can send an empty character with Chr (#0) or AnsiChar(#0) instead of '' that returns a null.