How to? COM class library Property pass-by-reference for TypeLib [propput] ?

T

TJ

In C# how do you achieve pass-by-reference property declarations in the Type
Library?

I am writing a COM Class Library that must mimick an existing library for
which the only information is the TypeLib. I'm using Visual Studio .NET
2003.

The original library provides simple authentication services, from Access
and MS-SQL OLEDB providers. The enhancement I'm creating provides support
for ODBC and will be a drop-in replacement.

The original library was written in Visual Basic 6, and declares an
interface with property setter methods that take pointers as arguments, as
this extract shows:

dispinterface _DBLink {
methods:
[id(0x60030006)] VARIANT_BOOL CheckUser([in, out] BSTR* User, [in, out]
BSTR* Password);
[id(0x68030008), propput] void LoginClassField([in, out] BSTR* rhs);

I've created an identical COM Class Library in C#, but the arguments to the
property setter methods are passed-by-value, and it isn't possible to use
the C# "ref" or "out" modifier on Properties.

dispinterface _DBLink {
methods:
[id(0x60020006)] VARIANT_BOOL CheckUser([in, out] BSTR* User, [in, out]
BSTR* Password);
[id(0x60020008), propput] void LoginClassField([in] BSTR rhs);

[id(0x60020009)] void set_LoginClassField([in, out] BSTR* value);

code:
public interface _DBLink {
bool CheckUser(ref string User, ref string Password);
string LoginPassField { set; }

void set_LoginClassField(ref string value);

// ... rest of code
}

As you can see, although I can define the Property setters as methods, the
TypeLib would then no longer declare them as "propput".

Any ideas?

TJ.
 
N

Nicholas Paldino [.NET/C# MVP]

TJ,

Why not use the TLBIMP utility and then reference that from your
project? I'm not too sure if the propput attribute in IDL is supported, but
if it is, TLBIMP should be able to create the correct managed assembly.

Hope this helps.
 
T

TJ

Nicholas, thanks for that suggestion, it offers some progress but further
confusion. I have further questions if anyone can help?

I'd been using Object Viewer stand-alone so it wasn't showing me C# specific
code matching the VB6 COM Type Library. As soon as I added the external
COM's TLB to my C# solution and ran Object Viewer on it, it showed:

IDL: [id(0x68030008), propput] void LoginClassField([in, out] BSTR* rhs);
C# prototype: public abstract new System.IntPtr LoginClassField [ set ]

I didn't know about the IntPtr structure. I've now redefined my Interface
this way:

public interface _DBLink
{
[DispId(0x68030008)] IntPtr LoginClassField{ set; }
// ...
}
public class DBLink : _DBLink {
public IntPtr LoginClassField {
set {
_LoginClassField = Marshal.PtrToStringBSTR(value);
Marshal.FreeBSTR(value);
}
}
// ...
}

Now I'm concerned that when viewed in Object Viewer the IDL looks different
in other ways:

public System.IntPtr LoginClassField [ set ]

and when used to view the resulting Type Library (from the build) shows:
[id(0x68030008), propput] void LoginClassField([in] long rhs);

So although the C# property declaration seems to match, the IDL looks
further away than it was. I've now got a long instead of a BSTR*

I tried to use [MarshalAs(UnmanagedType.LPStr)] as per the documentation,
but as for many things Property-related, the compiler won't accept it.

[DispId(0x68030008), MarshalAs(UnmanagedType.LPStr)] IntPtr
LoginClassField{ set; }

Is it possible to manually apply the propputref attribute to a C# method so
I could do something along the lines of this ?

[ BindAs( BindFlags.PutRefDispProperty ) ] public void
set_LoginClassField(ref string value)

which, if there is a way to control the Bind, would result in something like
this in the IDL:

[propputref] void LoginClassField( [in, out] BSTR* value)
 
C

Chad Myers

TJ,

I think Nicholas misunderstood your problem. It looks like he thought you
were trying to IMPORT a COM dll. If I now understand you correctly, you're
trying to EXPORT a .NET class as a COM interface to emulate an older VB6
component you had (i.e. you've re-written this component in C#, but you want
it to appear to clients as essentially the same interface, right)?

I've had to do similar things in my job recently. I've encountered a
situation similar to yours, though not exactly the same.

What I had to do is manually create my own IDL and my own TLB and, after
calling regasm /tlb, swap out the regasm-generated TLB with my homebrew TLB.

This is some complicated brain surgery, but in many cases, it's the only
thing you can do.

I've written several blog posts about doing this kind of stuff. Sorry for
the poor grammar and spelling, these were all written after midnight after a
long day. I need to re-write them, but I'm too lazy :)

Part 1: (poorly) explaining some of the concepts involved in exporting .NET
types to COM:
http://blog.chadmyers.com/chad/archive/2004/10/06/166.aspx

Part 2: Explaining problems with Tlbexp/TypeLibExporter and how to hack
together your own homebrew IDL:
http://blog.chadmyers.com/chad/archive/2004/10/07/167.aspx

Part 3: Handling ComVisible(true), default, reference-type properties in C#:
http://blog.chadmyers.com/chad/archive/2004/10/07/168.aspx


Part 3 is what my specific deal-breaker situation was. It's similar to your
situation. If you can find no other solution, you may have to follow some of
the steps in my Part 2 article to hack together your own IDL.

I've encountered a few other problems along the way that required IDL
manipulation, but I, sadly, haven't had time to blog about them.

I'd be happy to walk you through some of them once you've given everything
else a good try and studied up on IDL hacking.

I *STRONGLY* urge you to purchase, borrow, or otherwise acquire the book
".NET and COM The Complete Interoperability Guide" by Adam Nathan. This book
explains almost everything I did in those 3 articles. You may have to dig
around a little, but the information is all there.

One other thing, if you've had to deal with optional parameters in your COM
interface (i.e. the VB6 had some optional arguments, and now you have to
simulate that in the C# interface), check out:
http://blog.chadmyers.com/chad/archive/2004/02/10/145.aspx

Hope this helps.

-Chad


TJ said:
Nicholas, thanks for that suggestion, it offers some progress but further
confusion. I have further questions if anyone can help?

I'd been using Object Viewer stand-alone so it wasn't showing me C#
specific code matching the VB6 COM Type Library. As soon as I added the
external COM's TLB to my C# solution and ran Object Viewer on it, it
showed:

IDL: [id(0x68030008), propput] void LoginClassField([in, out] BSTR* rhs);
C# prototype: public abstract new System.IntPtr LoginClassField [ set ]

I didn't know about the IntPtr structure. I've now redefined my Interface
this way:

public interface _DBLink
{
[DispId(0x68030008)] IntPtr LoginClassField{ set; }
// ...
}
public class DBLink : _DBLink {
public IntPtr LoginClassField {
set {
_LoginClassField = Marshal.PtrToStringBSTR(value);
Marshal.FreeBSTR(value);
}
}
// ...
}

Now I'm concerned that when viewed in Object Viewer the IDL looks
different in other ways:

public System.IntPtr LoginClassField [ set ]

and when used to view the resulting Type Library (from the build) shows:
[id(0x68030008), propput] void LoginClassField([in] long rhs);

So although the C# property declaration seems to match, the IDL looks
further away than it was. I've now got a long instead of a BSTR*

I tried to use [MarshalAs(UnmanagedType.LPStr)] as per the documentation,
but as for many things Property-related, the compiler won't accept it.

[DispId(0x68030008), MarshalAs(UnmanagedType.LPStr)] IntPtr
LoginClassField{ set; }

Is it possible to manually apply the propputref attribute to a C# method
so I could do something along the lines of this ?

[ BindAs( BindFlags.PutRefDispProperty ) ] public void
set_LoginClassField(ref string value)

which, if there is a way to control the Bind, would result in something
like this in the IDL:

[propputref] void LoginClassField( [in, out] BSTR* value)
 
T

TJ

Thanks Chad, your articles have moved me on somewhat.

Working out what was wrong with the syntax of the Object Viewer IDL was a
pain, but then I had yet more joy solving "cl.exe not found" and "oaidl.idl
not found" errors from MIDL, along with others.

Solutions were to add to the environmental variables for the location of
midl.exe, cl.exe (wasn't even in the Visual Studio 2003 .Net folder
structure), and the library .tbl files used by MIDL.

PATH=%PATH%;C:\Program Files\Microsoft\Visual Studio .NET 2003\Common7\IDE;
PATH=%PATH%;C:\Program Files\Microsoft\Visual Studio .NET 2003\Vc7\bin;
INCLUDE=C:\Program Files\Microsoft\SDK\include\

*Note: I always edit my Microsoft applications install folder to be
"\Program Files\Microsoft\<application>" rather than "\Program
Files\Microsoft <application>" so as to stop the multiplication of
"Microsoft..." folders in the "Program Files" folder.

In the revised class IDL file express the full path for the importlib()
statement:

// TLib : // TLib : Common Language Runtime Library :
{BED7F4EA-1A96-11D2-8F08-00A0C9A6186D}
importlib("C:\WINDOWS\Microsoft.NET\Framework\v1.1.4322\mscorlib.tlb");

HOWEVER, having got this far I've now convinced myself I've taken the wrong
route, since the COM object I'm creating needs to be a drop-in replacement
for an existing COM object, and may be deployed on systems that don't have
the .Net framework installed.

What a pain, as the code in the library itself is complete!

I was investigating [ ComRegisterFunctionAttribute ] & [
ComUnregisterFunctionAttribute ] thinking I could create a component that
would work in a .Net and classic environment, but I think maybe I
misunderstood the documentation and that my component will only work in the
..Net environment, even when called by a classic COM client.

Thinking of switching to C++, but it seems that Visual Studio .Net 2003
won't let me create a classic COM library, only a .net assembly!
Investigating Visual Basic (*spits!) it looks like that still allows the
creation of classic COM libraries - grrr!!

I'll be glad when I've got this finished and can go back to Java and Eclipse
IDE! I only started on this C# / Microsoft programming 2 days ago to provide
ODBC functionality to the application that uses DBLink, so it can share a
common user database with a J2EE web application.

TJ.
 
C

Chad Myers

Re: Minor problems with midl:

Oh yeah, forgot to mention all the nitpicky stuff, but it's not that hard to
figure out or work through.

But then you said something that confused me...

HOWEVER, having got this far I've now convinced myself I've taken the
wrong route, since the COM object I'm creating needs to be a drop-in
replacement for an existing COM object, and may be deployed on
systems that don't have the .Net framework installed.

How do you plan on deploying, and running, a .NET/C# component on a system
without the .NET Framework installed? Isn't the whole point of this
excercise to replace a VB6 component with a C# component?
Thinking of switching to C++, but it seems that Visual Studio .Net 2003
won't let me create a classic COM library, only a .net assembly!
123456789112345678921234567893123456789412345678951234567896123456789712
I'll be glad when I've got this finished and can go back to Java and
Eclipse IDE! I only started on this C# / Microsoft programming 2 days
ago to provide ODBC functionality to the application that uses DBLink,
so it can share a common user database with a J2EE web application.

Ah, I see the problem. You are very confused. C# and VB.NET and Managed
Extensions for C++ are all .NET-framework based. Imagine if the JVM
supported multiple languages besides Java. That's what .NET essentially is.

You can create unmanaged components from VS.NET 2003, but only in C++
without the /clr compiler switch.

C# is a .NET-only language, you cannot create unmanaged code. You can create
a .NET component which exposes a COM interface, but you cannot create
non-.NET components from VS.NET 2003 if you're not using C++ [sans /clr].

Good luck on your efforts and I'm sorry you have to use Java/Eclipse ;)

-c



TJ said:
Thanks Chad, your articles have moved me on somewhat.

Working out what was wrong with the syntax of the Object Viewer IDL was a
pain, but then I had yet more joy solving "cl.exe not found" and
"oaidl.idl not found" errors from MIDL, along with others.

Solutions were to add to the environmental variables for the location of
midl.exe, cl.exe (wasn't even in the Visual Studio 2003 .Net folder
structure), and the library .tbl files used by MIDL.

PATH=%PATH%;C:\Program Files\Microsoft\Visual Studio .NET
2003\Common7\IDE;
PATH=%PATH%;C:\Program Files\Microsoft\Visual Studio .NET 2003\Vc7\bin;
INCLUDE=C:\Program Files\Microsoft\SDK\include\

*Note: I always edit my Microsoft applications install folder to be
"\Program Files\Microsoft\<application>" rather than "\Program
Files\Microsoft <application>" so as to stop the multiplication of
"Microsoft..." folders in the "Program Files" folder.

In the revised class IDL file express the full path for the importlib()
statement:

// TLib : // TLib : Common Language Runtime Library :
{BED7F4EA-1A96-11D2-8F08-00A0C9A6186D}
importlib("C:\WINDOWS\Microsoft.NET\Framework\v1.1.4322\mscorlib.tlb");

HOWEVER, having got this far I've now convinced myself I've taken the
wrong route, since the COM object I'm creating needs to be a drop-in
replacement for an existing COM object, and may be deployed on systems
that don't have the .Net framework installed.

What a pain, as the code in the library itself is complete!

I was investigating [ ComRegisterFunctionAttribute ] & [
ComUnregisterFunctionAttribute ] thinking I could create a component that
would work in a .Net and classic environment, but I think maybe I
misunderstood the documentation and that my component will only work in
the .Net environment, even when called by a classic COM client.

Thinking of switching to C++, but it seems that Visual Studio .Net 2003
won't let me create a classic COM library, only a .net assembly!
Investigating Visual Basic (*spits!) it looks like that still allows the
creation of classic COM libraries - grrr!!

I'll be glad when I've got this finished and can go back to Java and
Eclipse IDE! I only started on this C# / Microsoft programming 2 days ago
to provide ODBC functionality to the application that uses DBLink, so it
can share a common user database with a J2EE web application.

TJ.
 
T

TJ

The primary issue is, the COM DLL I'm "extending" is an optional part of a
Windows IRC server (IRCXPro), so for operators that want to use my ODBC
version of DBLink I can't dictate that they install the .Net Framework if it
isn't already there - a severe case of overkill I feel.

Assuming for the moment I continue with the current C# (how nice is it is to
see microsoft providing something so close to Java - the reason I was able
to get up to speed so quick)...

As I understand it, my managed-code replacement DLL can simply replace the
existing DLL, but am I correct in thinking the TLB would need to be added to
the DLL's resources? (I don't want to confuse users by shipping more than
the one file).

If thats the case, how is one supposed to add a resource to the DLL? I tried
to follow the destructions in the help files but I seem to hit a brick wall
no matter what approach I take - where the hell has the lovely ole Resource
Workshop gone for doing stuff like string-tables, icons, bitmaps, version
info, and raw RC_data?

Because the original DLL is set to OLESelfRegister does this have
implications for me? Do i need to implement the [
ComRegisterFunctionAttribute ] etc static methods and make entries in the
registry of the server the component is installed on?

Do I need to create an install package that simply makes the component
register itself, or is that handled the first time the DLL is called?

Because the finished component will use the same GUIDs as the original, in
theory it shouldn't need to register itself because all the settings will be
there already, but I just want to be clear.

Many thanks for your help on this; It's been an intense learning experience
and tiring on the eyes :cool:

TJ.
 
T

TJ

I thought I'd complete this thread with my latest experiences.

This article began as an appeal for help, but in the course of writing it I
appear to have solved the specific issue so check the article on my blog for
details.

When loading a VB6 application that delay-loads a database-access DLL
written in C# I'm seeing the error dialog
--------------------------------
Two different copies of MSCOREE.DLL have been loaded.
First copy:
<Unknown>
Second copy:
C:\Windows\Microsoft.NET\Framework\v1.1.4322\mscorwks.dll
This is typically caused by having a registered MSCOREE.DLL that is
different from one that is statically linked with the application.
------------------------

It is a 3rd-party application that recently added support for user
authentication via MS Access and MS SQL Server databases using a
delay-loaded DLL.

The DLL exposes its interfaces via COM and its TLB is in the resource
section of the DLL. I used OLE/COM Viewer to extract the IDL and wrote my
own C# component that supports ODBC datasources, with an identical type
library so it can be used as a drop-in replacement for the application's own
database-access DLL.

http://blog.tjworld.net/blog/page/TJ/20050622#two_different_copies_of_mscoree
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top