Question

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?

Was it helpful?

Solution

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.

OTHER TIPS

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.

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

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

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top