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 = interfa
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.
To avoid errors by null pointers you can send an empty character with Chr (#0) or AnsiChar(#0) instead of '' that returns a null.
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.
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