views:

2007

answers:

8

I need to use a COM component (a dll) developed in Delphi ages ago. The problem is: the dll does not contain a type library... and every interop feature (eg. TlbImp) in .NET seem to rely on TLBs. The component has been used in Delphi programs here for many years without problems because "It's not much of a problem using COM objects from Delphi, because we know the interfaces" (quote Delphi developer).

Is there any way I can use this DLL from c# without a TLB? I've tried using the DLL as unmanaged, but the only method it exports are DllUnregisterServer, DllRegisterServer, DllCanUnloadNow and DllGetClassObject. I know the names of the classes and functions I'm going to use, if that can be of any help.

UPDATE: I've tried implementing Jeff's suggestion, but I'm getting this error:

*"Unable to cast COM object of type 'ComTest.ResSrvDll' to interface type 'ComTest.IResSrvDll'. This operation failed because the QueryInterface call on the COM component for the interface with IID '{75400500-939F-11D4-9E44-0050040CE72C}' failed due to the following error: No such interface supported (Exception from HRESULT: 0x80004002 (E_NOINTERFACE))."*

This is what I've done:

I got this interface definition from one of the Delphi-guys:

unit ResSrvDllIf;

interface

type
   IResSrvDll = interface
   ['{75400500-939F-11D4-9E44-0050040CE72C}']
    procedure clearAll;

    function  ResObjOpen(const aClientID: WideString; const aClientSubID: WideString;
                         const aResFileName: WideString; aResShared: Integer): Integer; {safecall;}
    ...
   end;
implementation
end.

From this I've made this interface

using System.Runtime.InteropServices;
namespace ComTest
{
    [ComImport]
    [Guid("75400500-939F-11D4-9E44-0050040CE72C")]
    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    public interface IResSrvDll
    {
     int ResObjOpen(string aClientID, string aClientSubID, string aResFileName, int aResShared);

    }
}

And this coclass (got the guid from the delphi-guys)

using System.Runtime.InteropServices;

namespace ComTest
{
    [ComImport]
    [Guid("75400503-939F-11D4-9E44-0050040CE72C")]
    public class ResSrvDll
    {
    }
}

UPDATE

The solution from Jeff is the way to do it. It is worth noticing, though, that the interface definition must match the COM-components exactly! ie. same order, same names, etc.

A: 

Look at Regasm.exe

Preet Sangha
RegAsm doesn't work on WIN32 libraries that don't have type libraries... It needs the TLB to know the definitions of the classes and methods.
Workshop Alex
Thanks. That's plugged a gap.
Preet Sangha
Tried it - didn't work... Thanks for your answer, though :-)
toxvaerd
+1  A: 

You can also do late binding and then invoke methods through reflection (myObject.InvokeMember("NameOfTheMethod", options, params, etc.)).

A wrapper should, however, offer better performance and faster marshalling.

Groo
That requires the object to support late binding. Not all objects implement IDispatch.
Rob Kennedy
Given the error messages he's getting now, it may be that the object ONLY supports IDispatch!
Daniel Earwicker
+1  A: 

Yes and no.

All C# (and any CLR language) needs in order communicate with a COM object is a compatible interface signature. Typically specifying the methods, GUID and apartment style of the interface. If you can add this definition into your code base then the TLB is not necessary.

There is a small caveat that comes with that statement. I believe you will get into trouble if you try and use a COM object across apartment boundaries and don't have a suitable TLB registered. I can't 100% remember on this point though.

JaredPar
+2  A: 

Write a wrapper in VB.Net. VB.Net supports true late binding (no messy reflection). All you need is the progId. You should also implement IDisposable to explicitely manage the component lifecycle.

John Gwynn
I don't know VB, and would like to avoid putting any more languages in the mix. It should be possible to find a solution using only C#. But thanks for your answer :-)
toxvaerd
A: 

I suspect that the dynamic keyword (C# 4.0) will accomplish this. If it does, it will give results which are largely equivalent to invoking the methods, i.e. how Groo suggests.

Brian
+8  A: 

You just need the CLS_ID and interface id. I wrote about this specific issue on my blog:

"Using Obscure Windows COM APIs in .NET"

Jeff Moser
This looks promissing... I'll try it out!
toxvaerd
+2  A: 

It is quite frequent that you will encounter an interface implementation that is not backed by a type library (Delphi or otherwise). Shell extensions are one example.

You basically need to make a Windows API call to create the instance through the proper COM function calls. The API will take care of managing the DLL via the exported functions you mentioned earlier.

You will need to recreate the interface definition in C# code but after that you simply create the object, cast it to the interface, and it is no different than anything else. The only real caveat here is, depending on your usage, you may have some threading issues to deal with so check the "threading model" that was used for the DLL and consider your usage based on that.

Here is a link to a tutorial on consuming the interfaces that are not TLB based. Tutorial

Ryan VanIderstine
A: 

If you've managed to create an instance of the object, you're over the first major hurdle!

Now try this:

myObject.GetType().InvokeMember(
                      "ResObjOpen",  // method name goes here
                      BindingFlags.InvokeMethod,
                      null,
                      myObject,
                      new object[] { 
                         someClientID,   // arguments go here
                         someSubId, 
                         somFileName, 
                         someInt} );

The reason I think you may need to do this is if the Delphi COM object is not a "dual" object. It may only support late binding, i.e. the kind of invocation you see above.

(In C# 4.0 they're making this easier with the dynamic keyword.)

EDIT: Just noticed something very suspicious. The IID for the interface and the CLSID for the object itself appear to be the same. That's not right.

Given that you've succeeded in creating the object, it would appear to be the CLSID of the object. So it's not the right IID. You need to go back to your Delphi folks and ask them to tell you what the IID of the interface IResSrvDll is.

Edit again: You could try changing the enum member you specify from ComInterfaceType. There should be ones for IDispatch and "dual" - although as your object doesn't support IDispatch, neither of those should be the right choice. The IUnknown setting (which appears in your sample code) should work - suggesting that the IID is wrong.

Daniel Earwicker
Well - I've managed to create an instance, yes, but I can't cast it to the interface type.
toxvaerd
Right - suggesting strongly that it doesn't support that interface, not literally. It may only support a low-level interface called IDispatch. The code sample above works internally by using IDispatch to make the call. Have you tried it?
Daniel Earwicker
This gives me a "COM target does not implement IDispatch.". Futhermore I would like to avoid using reflection.
toxvaerd
See edit - it must be the wrong IID then.
Daniel Earwicker
Actually they're not the same... I suspected the same thing, but the 8th digit differ. The delphi-guys said it was just how Delphi created guids.
toxvaerd
Ah, my mistake... even so, CLR isn't lying to you. That object doesn't support that interface.
Daniel Earwicker