0

I am developing a COM Dll from existing Delphi legacy code to be used in C#.NET. I have added proper Interfaces to the Type Library and the COM server is successfully registered and objects can be seen and created from the C# environment. However, some of the existing classes have customized Constructors which are necessary. So, I added a Helper class (also as a COM object) which contains methods which are supposed to construct and return other COM objects, but the return type is incompatible with the created object and the program crashes.

I am using COM object wizard and the code is pretty much generated by Delphi itself. For example, by defining a ISomeObject Interface, its corresponding CoClass named SomeObject and a Delphi class TSomeObject are generated. TSomeObject implements ISomeObject includes implementation of properties and methods. My intention is to create an instance of TSomeObject, using the Helper object and use it in C# environment.

The C# code that creates the Helper object and its method is as follows:

    Helper helper = new Helper;
    SomeObject = helper.CreateSomeObject(Param1, Param2);

When I add a method with the return type set to SomeObject to the IHelper interface in Type Library, the following code (minus the body) is generated.

function THelper.CreateSomeObject(Param1, Param2): SomeObject
begin
    Result := TSomeProject.Create(Param1, Param2); //This line is not generated by COM Object Wizard
end;

The code above crashes with an error due to incompatibility.

While debugging, I realized that the type of Result is Pointer as ISomeObject. I have tried to Type Cast the output of TSomeProject.Create to SomeProject using as operand, which did not work.

The question is how can I return an instance of TSomeObject through a method which its return type is SomeObject.

0

1 Answer 1

1

Change your method signature to return the interface rather than instance of SomeObject class. You can do that in type library editor like this: enter image description here

The generated code would then be:

function THelper.CreateSomeObject: ISomeObject;
begin

end;

Edit 1

As per comment: Although you've provided a lot of text in your question, it still lacks some essential information. You've mentioned some pointer operations, but that isn't something that you are supposed to deal with in a typical scenario when developing a COM server.

So I tried to re-create your scenario myself in Delphi 7 (oldest delphi version that I have installed). I created a COM server (ActiveX Library project in Delphi) similar to the one above. My implementation of method CreateSomeObject was:

function THelper.CreateSomeObject: ISomeObject;
begin
  Result := TSomeObject.Create;
end;

Implementation of method TSomeObject.HelloWorld is unimportant. Then I registered the server via IDE function Run > Register ActiveX Server. After that I created sample console application in Delphi, imported type library (Project > Import Type Library) and added few lines of code to main program:

uses
  ActiveX, COMTest_TLB;

var
  _Helper: IHelper;
  _SomeObject: ISomeObject;
begin
  CoInitializeEx(nil, COINIT_APARTMENTTHREADED);
  _Helper := CoHelper.Create;
  _SomeObject := _Helper.CreateSomeObject;
  _SomeObject.HelloWorld;
end.

The console application ran to completion without a crash or unexpected result. So far so good. Then I created sample C#.NET console application (.NET 4.5.2) with reference to my COMTest library:

using System;
using COMTest;

class Program
{
    [STAThread]
    static void Main(string[] args)
    {
        var helper = new Helper();
        var someObject = helper.CreateSomeObject();
        someObject.HelloWorld();
    }
}

And indeed the application was crashing with AccessViolationException. I quickly set up debugging of COM server by setting host application to .NET console application and enabling remote debug symbols in project's linker options (I'm sure you've figured that out already). Creation of TSomeObject instance went smoothly, but the assignment to Result failed.

There's some compiler magic when assigning value to a variable of managed type (interface in this case). It starts with clearing the destination which basically is a call to _Release, if the destination is not nil. And to my surprise in case of .NET console app client it wasn't! So I amended the implementation to:

function THelper.CreateSomeObject: ISomeObject;
begin
  Pointer(Result) := nil;
  Result := TSomeObject.Create;
end;

Clearing the result before its first usage as interface did the trick. I haven't dug into detail on why that happens, but I will certainly do. I'll also check it with newer Delphi version and publish my findings in another edit of this answer.

Here you can find some great resources related to COM development in Delphi. http://www.techvanguards.com/com/

Disclaimer: I'm not related to that site in any way, I just find it incredibly useful.

Edit 2

COM interface methods should return HRESULT by convention. That's the default mechanism in COM to report errors. Returning additional value(s) from a method should be implemented via parameters with [Out] modifier. Alternatively a parameter can be marked with [Out, RetVal] modifier (usually the last one) to indicate the return value of the method. Note that [Out] parameters are passed by reference, which you must indicate with additional asterisk (*) symbol appended to type name in type library editor. So ISomeObject* becomes [Out] ISomeObject**.

Delphi supports safecall calling convention and thus it can eliminate HRESULT return value from your method signature by wrapping any uncaught exception in your method and passing it back in EAX register allowing the [Out, RetVal] parameter to become the return value. But this is only supported for dual interfaces (see Flags tab of an interface) that are derived from IDispatch. To avoid implementing IDispatch methods you can convert your lightweight COM object to automation object (TAutoObject), if you haven't done that already. This can be achieved by selecting 'Automation Object' option instead of 'COM Object' when adding new item to your ActiveX library.

So this is how the method definition looks like when converted to safecall: Type library editor

And here's generated code with trivial implementation:

type
  THelper = class(TAutoObject, IHelper)
  protected
    function CreateSomeObject: ISomeObject; safecall;
  end;

function THelper.CreateSomeObject: ISomeObject;
begin
  Result := TSomeObject.Create;
end;
Sign up to request clarification or add additional context in comments.

3 Comments

Thank you @Peter Wolf for your response, but I have already tried it, it won't work.
"Won't work" is not a useful problem description. Why do you expect it not to work, without apparently even trying it?
Dear @Peter, Thank you for your thorough answer and all the trouble you have had to simulate the problem. I really appreciate it. Your solution totally works! I am not familiar with automation object, but I will definitely try it out.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.