3

i'm define in C# this interface for a COM-Server:

[ComVisible(true)]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[Guid("58C77969-0E7D-3778-9999-B7716E4E1111")]
public interface IMyInterface    
{
    string MyName { get; }
}

This interface is imported and implemented in a Delphi XE5 program.

The import looks like this:

IMyInterface = interface(IUnknown)
  ['{58C77969-0E7D-3778-9999-B7716E4E1111}']
  function Get_MyName (out pRetVal: WideString): HResult; stdcall;
end;

The implementation like this:

type
  TMyImpl = class(TInterfacedObject, IMyInterface)
  public
    function Get_MyName (out pRetVal: WideString): HResult; stdcall;    
 end;

 function TMyImpl.Get_MyName (out pRetVal: WideString): HResult;
 var
  s: string;
 begin
   s:=''; // empty!
   pRetVal:=s;
   result:=S_OK;
 end;

When i call that server from c# like this:

var server = new Server();
string s = server.MyName;

Then s is NULL and not an empty string as excepted.

How i can force that empty strings are transferred in COM as empty string and not replace by marshaling to NULL?

4
  • Not a duplicate, IMO. The other question only asks for explanation why it's received as null. This one asks how to force an empty string. Commented Jan 8, 2016 at 16:36
  • @TOndrej Well, given that the asker marked it as a dupe, I think that's OK. And linking the questions is good. Commented Jan 8, 2016 at 16:46
  • It's fine by me, too. It's good to have them linked. Commented Jan 8, 2016 at 16:48
  • That was also my idea. unfortunately i found is past my question. Commented Jan 11, 2016 at 8:03

2 Answers 2

3

Delphi implements empty strings as nil pointers (see System._NewUnicodeString). You can allocate an empty COM-compatible string manually:

function TMyImpl.Get_MyName(out pRetVal: WideString): HResult;
var
  BStr: TBstr;
begin
  BStr := SysAllocString('');
  if Assigned(BStr) then
  begin
    Pointer(pRetVal) := BStr;
    Result := S_OK;
  end
  else
    Result := E_FAIL;
end;

or you could create a helper function:

function EmptyWideString: WideString;
begin
  Pointer(Result) := SysAllocString('');
end;
Sign up to request clarification or add additional context in comments.

6 Comments

You must not destroy the string. Ownership passes to the caller. Unless a copy is made. Perhaps it is. In which case I'm talking nonsense. In anycase it's surely easier to avoid WideString here.
@DavidHeffernan Correct, thanks! Removed freeing the string. But SysAllocString can fail.
I don't see much point of using try/finally here. The function is contracted not to raise. It would seem cleaner to move pRetVal := ''; Result := E_FAIL; into an else statement and remove the try/finally. Ok, I see your edit, I think that's cleaner. To be honest, if SysAllocString fails, then we're hosed, but I do take your point. Personally I think that's something I'd be prepared to assume always works. But I recognise that other views are valid.
Hi, i tested your solution. The conversion pRetVal := Bstr; will cause again a null pointer if the string is empty. Widestring as parameter typ seems to be a problem. but this i can't change, because interface is generated from c# tlb.
try typecasting: Pointer(pRetVal) := BStr;
|
2

Try this on the Delphi side:

IMyInterface = interface(IUnknown)
  ['{58C77969-0E7D-3778-9999-B7716E4E1111}']
  function Get_MyName (out pRetVal: BSTR): HResult; stdcall;
end;

function TMyImpl.Get_MyName (out pRetVal: BSTR): HResult;
begin
  pRetVal := SysAllocString('');
  Result := S_OK;
end;

If you wish to handle the case where SysAllocString fails then you would write it like this:

function TMyImpl.Get_MyName (out pRetVal: BSTR): HResult;
begin
  pRetVal := SysAllocString('');
  Result := IfThen(Assigned(pRetVal), S_OK, E_FAIL);
end;

Although personally I feel that it is reasonable to draw the line at check for errors on a call to SysAllocString('').

My guess is that Delphi marshals an empty WideString as a nil pointer rather than an empty BSTR. Which in my view is a defect.

4 Comments

Hi, your solutins using TBStr works. But the declarations are made in C# and the TLB is imported to Delphi with tlbimp.exe. Now i need an idea how i can force that the tlb import using tbstr instead of widestring without modify the import every time when its generated.
I think you need to post process the imported interface
Using dotnet framework RegAsm.exe csharp.dll /tlb then with delphi's tlibimp.exe -P+ -Ha- -Hr- -Hs- -DImports csharp.tlb the result you can see in my question. this is part of an build script, which compiles that c# stuff first, followed by the import, then compile of delphi stuff.
Indeed. So post-process the imported interface. Or use a cast as TOndrej shows you. I think your question has been answered now and you are moving onto the specifics of what you do with the answer. That's fundamentally not the remit of this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.