Groups | Blog | Home
all groups > dotnet interop > april 2006 >

dotnet interop : C++ dll1 calls c#-wrapper-dll calls C++ dll2


MSDNAndi
4/13/2006 9:08:02 AM
Hi,
I have the following situation:
I have one C++-dll that I am not allowed to touch (dll1)
dll1 calls a C++ dll2.
Now, I have to "plug" myself in with a c# dll. I can change dll2.
The reason is, that functionality in dll2 will over time be implemented in
C# (wrapper DLL) and over time less and less calls will be passed through -
however, it is a pity we cannot touch dll1.

Dll1 does:
MYDBLib::IDatabaseAccessLayerPtr spDBAccess;
hr = spDBAccess.CreateInstance( MyDBLib::CLSID_DatabaseAccessLayer );
and then calls like
hr = spDBAccess->doSomething(&pbstrMyString1, &pbstrMyString2);
or
hr = spDBAccess->doSomethingElse((BSTR)pbstrParameters, &pbstrOut,
&bstrError);

What did I do so far:
I gave DLL2 completely new GUIDs.
Then I made a C# wrapper like this:
[ComVisible(true)]
[Guid("010FA7E6-ABDF-4ECE-BC11-609A16713F4D")]
[InterfaceType(ComInterfaceType.InterfaceIsDual)] //I tried the other
options also
MyDBLib.IDatabaseAccessLayer
public interface IDatabaseAccessLayerWrapper // before it had a :
InheritFromTheOldDll1sInterface
{
//...
void doSomething(out string pbstrMyString1, out string
pbstrMyString2);
void doSomethingElse(string pbstrParameters, out string pbstrOut,
out string pbstrFehler);
//..
}
The interface is complete... I created it by first "inheriting" from the
original DLL-interface (by referencing the c++-based DLL), then having a
class implementing the interface, then using VS2005 "implement interface"
functionality... then used "extract interface".

Now for the rest:
[ComVisible(true)]
[Guid("8D4B28E8-C132-4427-9ED6-B4BFC33C33CA")]
[ClassInterface(ClassInterfaceType.AutoDual)] //tried the other Options
[ProgId("MyDB.DatabaseAccessLayer.1")]
public class DatabaseAccessLayerWrapper : IDatabaseAccessLayerWrapper
{

private MYDBLib.DatabaseAccessLayer oDBAccess; //might switch that
to static later

public DatabaseAccessLayerWrapper()
{
//constructor logic
//might change to use a static oDBAccess-Object in the future
oDBAccess=new DatabaseAccessLayer();
}

public void doSomething(out string pbstrMyString1, out string pbstrMyString2)
{
oDBAccess.doSomething(out pbstrMyString1, out pbstrMyString2);
}
public void doSomethingElse(string pbstrParameters, out string pbstrOut, out
string pbstrFehler)
{
oDBAccess.doSomethingElse(pbstrParameters, out pbstrOut, out pbstrFehler);
}
}


Now...I can access the methods of the wrapper properly doing late binding by
using the progid "MyDB.DatabaseAccessLayer.1" it seems. (I did not check if
the out-parameters are handled properly though since I tested in Scripting
Host JScript and JScript does not support out parameters).
However, from C++ dll (that I cannot even recomplile with a new type library
import since this would be a new dll), the wrapper gets instantiated, the
dll2 gets instantiated, but the calls do not seem to work.
After adding debugging output code, I see that the dll1 calls into wrong
method calls of my wrapper-dll and sometimes the method calls do not seem to
happen at all.

How can I enforce that my c# DLL has the exact same order in the interface
than in the original C++-based dll?
Can I rely on that the interface will be the same order as I read from the
top in the C++-IDL? Would it be enough to simply have the same order in my
interface? Do I also have the same order in my implementation of the
interface (since they are public methods)?
Or do I e.g. use DispId? (If so, which number to start? do the constructors
destructors/finalizers count also?)



Further:
If I would want to mimick the DLL2 completely in C#, how do I achieve this,
since I cannot set a "version independent progid" in C# and I cannot give a
GUID for the type library it seems either.

Do I need to use "out" parameters or can I use "ref"erences also in any way?

I hope I gave enough information so someone can get me on the right track...

Please remember that I cannot change DLL1 at all.


Thanks,

MSDNAndi
4/13/2006 9:17:11 AM
[quoted text, click to view]
I should have mentioned that those Guids you see here are the Guids of the
original Dll (from the .rgs and .idl).

v-phuang NO[at]SPAM online.microsoft.com (
4/14/2006 12:00:00 AM
Hi

From your descirption, I understand your scenario as below.
DLL1 ----> C#----->Dll2
But you have no DLL1 source code.

It seems to be a complex scenario, because we did not know for sure how the
DLL1 call into C# dll.

But with dll2, I think you may try to compare the TLB file of C# DLL with
the Dll2 original TLB to see if there is any difference.
Please compare carefully, check the GUIDs, Interface names, and method
name(method order) .....

Please have a check.and let me know the result.

Thanks!

Best regards,

Peter Huang

Microsoft Online Community Support
==================================================
When responding to posts, please "Reply to Group" via your newsreader so
that others may learn and benefit from your issue.
==================================================
This posting is provided "AS IS" with no warranties, and confers no rights.
MSDNAndi
4/14/2006 4:20:02 AM
Hi Peter,

it is obvious that the c#-dll differs from the C++-dll2 on the interface
level. However, unfortunately I cannot compare the .tlb files for the next
few days due to a long holiday weekend here. (I need to get it to work though
ASAP after that).

One of the questions was what I can do to make the c# dll most exactly like
the c++-dll2.
Can you point me in the right direction?
requote: "How can I enforce that my c# DLL has the exact same order in the
interface
than in the original C++-based dll?
Can I rely on that the interface will be the same order as I read from the
top in the C++-IDL? Would it be enough to simply have the same order in my
interface? Do I also have the same order in my implementation of the
interface (since they are public methods)?
Or do I e.g. use DispId? (If so, which number to start? do the constructors
destructors/finalizers count also?)

Further:
If I would want to mimick the DLL2 completely in C#, how do I achieve this,
since I cannot set a "version independent progid" in C# and I cannot give a
GUID for the type library it seems either.

Do I need to use "out" parameters or can I use "ref"erences also in any way? "


At this point especially the interface order looks suspicious - but what
procedure to enforce the same interface order from c++ to c# can I rely on?
The c++ code does describe the interface in the IDL in a specific order (but
does not explicitly say "this is method 1", "this is method2")- how can I
make sure that the output of the Visual Studio 2005 with the C# compiler will
provide the same interface order (after using RegAsm)?
Will the order the c# sourcecode of the interface definition or of the order
of the public methods of the class implementing the interface count? Or do I
have to set specific attributes (would DispId do the trick)?


Kind regards,




[quoted text, click to view]
v-phuang NO[at]SPAM online.microsoft.com (
4/17/2006 12:00:00 AM
Dear Customer,

Thanks for your quickly reply!
I know that would be a tedious way to compare a large TLB file.
But as I said before, we did not have the source file for DLL1, we did not
know how it make the call.
The only clue we have now is the TLB file of DLL2, which is the standard
that how the COM call will occur. So we have to mimic the DLL2's TLB.

I suggest you tried to define the Interface and method in the order that
you get from the TLB file of DLL2. Although I do not think the order will
make much difference, we are trying to mimic the DLL2's behavior.
As for the DispatchID, commonly it is used by late binding. Since we did
not know how the C++ DLL2 works, without source code, it is a hard
troubleshooting and supported scenario.

We will notice that in the TLB file, the method will have a dispatch id, we
can just use that value.
e.g. Here is some IDL got from Office tlb file, every method have a
[id(0xffffec78), propget, hidden] which is the dispid.
dispinterface _CommandBarComboBox {
properties:
methods:
[id(0xffffec78), propget, hidden]
IDispatch* accParent();
[id(0xffffec77), propget, hidden]
long accChildCount();
[id(0xffffec76), propget, hidden]
IDispatch* accChild([in] VARIANT varChild);
[id(0xffffec75), propget, hidden]
BSTR accName([in, optional] VARIANT varChild);
[id(0xffffec74), propget, hidden]
BSTR accValue([in, optional] VARIANT varChild);
[id(0xffffec73), propget, hidden]
BSTR accDescription([in, optional] VARIANT

Here is some code for your reference,
Just note the Example about how to mimic a tlb in C# code.
How to: Create Wrappers Manually
http://msdn2.microsoft.com/en-us/library/x8fbsf00(VS.80).aspx



Best regards,

Peter Huang

Microsoft Online Community Support
==================================================
When responding to posts, please "Reply to Group" via your newsreader so
that others may learn and benefit from your issue.
==================================================
This posting is provided "AS IS" with no warranties, and confers no rights.
MSDNAndi
4/17/2006 1:08:01 AM
Hi Peter,

this thread is getting longer than I thought - maybe I am not precise enough.

actually, I never said that there is no source for dll1. However, it is not
under my control and I

cannot "touch" it.

Code snippets from dll1 code were posted already in this thread:
"
MYDBLib::IDatabaseAccessLayerPtr spDBAccess;
hr = spDBAccess.CreateInstance( MyDBLib::CLSID_DatabaseAccessLayer );
and then calls like
hr = spDBAccess->doSomething(&pbstrMyString1, &pbstrMyString2);
or
hr = spDBAccess->doSomethingElse((BSTR)pbstrParameters, &pbstrOut,
&bstrError);
"
I cannot put the original code in the newsgroup.

The example you posted did not 100% answer how to mimick an existing
interface.
Remember, of dll2 that I want to mimick, I have IDL and I have a TLB, and a
..rgs .

As I said I cannot recompile the dll1 original code against a new tlb (or
even new GUIDs). (I do not know if dll1

actually compiles but that is a different issue - I may have only part of
the source tree)

Since some calls actually happen - but into the wrong methods - I am pretty
solid about that it

must have something must be messed up in the interface order.
The code of dll1, as we have seen, binds against the GUIDs of DLL2 at
compilation time.
My C# dll will have the GUIDs of the original DLL2 (the DLL2 GUIDs I needed
to change, so the .rgs was also changed), so that the DLL1 knows what to call.

Are those types of calls
"hr = spDBAccess->doSomething(&pbstrMyString1, &pbstrMyString2);"
not compiled in a way that the interface is expected to be in a specific
order? Lets forget about

the individual compiler for a second: To know what to do - not thinking of
the language - there are

those ways how to determine what to call (ID shall stand for any type of
symbolic representation,

be it a number, a GUID or a different name, but generated by the compiler
(of dll2) ):
"by method name and order"
"by method name and signature"
"by method name and signature ID"
"by method name and ID"
"by an ID" (unique over method names and signatures)
"by an ID and order"
"by an ID and signature ID" (one ID for the method names, one ID for the
signature)
"by an ID and signature"
"by order"

My guess is that the calls work by order, since there are no duplicated
signatures for the same

method calls, some method calls actually happen but into the wrong methods.

So my question is:
How can I (relatively easily) enforce in a new C# dll that it has the same
interface like an old

C++ DLL (of which I have IDL and TLB). But not try-and-error by comparing
the TLBs.
There _must_ be a documented way on the source code level from (unmanaged,
VC6 or converted to VS2005) C++ IDL (+.rgs) to C#.
Ideally not using tools, but rather going from the IDL source to

the corresponding C# source code step by step. (First we should understand
what to do exactly

before try to use a tool that generates some wrapper that we have to modifiy
but we do not know how

to modify since we do not understand the process to get there).
(Going through .tlb is not on the source code level.)

If this is too hard to find, then I am happy with a relatively simple example.

It should work for a default constructor (ideally also a
finalizer/destructor pairing without diving too deep in all aspects of this -
I am aware that the garbage collector handles the finalizer at different
points in time than destructors get called), 2 methods (so that the suspected
"order"-problem hopefully is addressed - and not just works by coincidence)
and an actual dummy c# implementation of the methods of the interface

(with "void" return types on the C# side)?


Restriction: What I still wanted to use is not to provide explicit HRESULT
return values but use

the "void" return value and have the interop give the proper HRESULT
according to the exception to

the caller.



Thanks,

(If you understand what I mean no need to read further...)




....

Since the example you gave talks about "RCW", I am a little confused,
because that term implies to

talk about a wrapper for a COM assembly that can be called from .NET. Isn't
that the code-basis for the interop.something.dll that is generated when I
add a reference in Visual Studio to a COM-DLL?
(Also - the example there does not seem to mimick the GUIDs of the original
DLL but rather wraps a call to the original DLL in a new DLL with a new GUID.)
However, there is a link in that article:
"http://msdn2.microsoft.com/en-us/library/k83zzh38(VS.80).aspx"
"http://msdn2.microsoft.com/en-us/library/xwzy44e4(VS.80).aspx"
I understood the examples all in the way that after following that then have
wrappers for the type library or the DLL

that I then can use from inside the .NET-framework but not to completely
replace a COM interface.
Does the process really mimick the original IDL/DLL/type library (with
keeping the GUIDs to the most possible extend)?
The example in
"http://msdn2.microsoft.com/en-us/library/x8fbsf00(VS.80).aspx" that you
pointed to does not do

that.

Instead of something like:
" extern int ISATest.InSArray( [MarshalAs(UnmanagedType.SafeArray,
SafeArraySubType=VarEnum.VT_I4)] ref int[] param );
"

Since my types are fairly simply and Visual Studio creates Interop Wrappers
with "out string" parameters and simple types (most complex type - used
rarely - is an ENUM-type).
I would hope to see something like...

....
[someattributes here that can be determined by looking at the IDL]
void MyMethod2(out string someParameterHere,...)
{...
}
[someattributes here]
void MyMethod1(string someParameterHere,out string someparameterthere,...)
{...
}


v-phuang NO[at]SPAM online.microsoft.com (
4/18/2006 12:00:00 AM
Dear Customer,

Thanks for your quickly reply!
Here I build a simple sample, hope that will help you to understand my
suggestion.
1. DLL1, Here I write a C++ Console Applicaiton, because it is a caller.
#import "..\\ATLDllTest\\Debug\\ATLDllTest.tlb" raw_interfaces_only
using namespace ATLDllTestLib;
using namespace std;
int _tmain(int argc, _TCHAR* argv[])
{
CoInitialize(NULL);
HRESULT hr;
ITestObjPtr pTestObj;
pTestObj.CreateInstance(__uuidof(TestObj));
CComBSTR _bstr("ABC");
CComBSTR rtBSTR;
hr= pTestObj->DoSomething(_bstr,&rtBSTR);
if(FAILED(hr))
{
cout<<"COM Error"<<endl;
}
cout<<CW2A(rtBSTR.Detach())<<endl;
CoUninitialize();
return 0;
}

2. DLL2, a Test ATL project.
[
object,
uuid(075102B3-6F41-4D86-BF40-B3517219FB9B),
dual,
nonextensible,
helpstring("ITestObj Interface"),
pointer_default(unique)
]
interface ITestObj : IDispatch{
[id(1), helpstring("method DoSomething")] HRESULT DoSomething([in] BSTR
inStr, [out,retval] BSTR* outStr);
};
[
uuid(EF9A4D88-2F43-420D-8915-18DB5378C8CB),
version(1.0),
helpstring("ATLDllTest 1.0 Type Library")
]
library ATLDllTestLib
{
importlib("stdole2.tlb");
[
uuid(18CD3B86-AA2A-4553-B4E4-53E3B3CCAA7A),
helpstring("TestObj Class")
]
coclass TestObj
{
[default] interface ITestObj;
};
};


STDMETHODIMP CTestObj::DoSomething(BSTR inStr, BSTR* outStr)
{
ATLTRACE("Test %S", inStr);
CComBSTR _bstr(inStr);
_bstr.Append(" Added in unmanaged DLL");
*outStr = _bstr.Detach();
return S_OK;
}


NOW, the Console will call the DLL2 successfully.
So we did not touch the Console and DLL2.
I just unregister the DLL2, because we are going to register the C# DLL
with the SAME GUID.


3. Here I mimic a C# DLL with the almost SAME exported IDL.
using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;

namespace TestCSLib
{
[InterfaceType(ComInterfaceType.InterfaceIsDual)]
[Guid("075102B3-6F41-4D86-BF40-B3517219FB9B")]
[ComVisible(true)]
public interface ITestObj
{
[DispId(0x00000001)]
[return:MarshalAs(UnmanagedType.BStr)]
string DoSomething([MarshalAs(UnmanagedType.BStr)]string inBSTR);
}
[ClassInterface(ClassInterfaceType.None)]
[Guid("18CD3B86-AA2A-4553-B4E4-53E3B3CCAA7A")]
[ComVisible(true)]
[ProgId("ATLDllTest.TestObj")]
[ComDefaultInterface(typeof(ITestObj))]
public class TestObj: ITestObj
{
[DispId(0x00000001)]
[return: MarshalAs(UnmanagedType.BStr)]
public string DoSomething([MarshalAs(UnmanagedType.BStr)]string
inBSTR)
{
return inBSTR + " Added in managed C#";
}
}
}

Now register the C# DLL to COM, The Console Application will call the C#
application successfully.

Please have a check. If you want, please let me know your email, so that I
can send the whole solution to you.

Best regards,

Peter Huang

Microsoft Online Community Support
==================================================
When responding to posts, please "Reply to Group" via your newsreader so
that others may learn and benefit from your issue.
==================================================
This posting is provided "AS IS" with no warranties, and confers no rights.
MSDNAndi
4/18/2006 12:59:22 AM
Dear Peter,

thanks for the example, but again: how about more than one method.
As I said -in my case - calls were being made, but to the wrong methods.
(Try putting the methods in C# in a different order)
You mention "exported IDL"?!
Or do you mean "TLB"?
If you meant IDL, please elaborate.

How do I ensure now the same order in the interface when dealing with C#
with multiple methods.
Will the compiler just take the order in the order I provide it or will it
change the order somehow?

(When adding a DLL reference in VS and having VS provide an empty
implementation construct an interface inherited from that interface, the
methods will be ordered by alphabet...)

Does the COM+-Interop wrapper take the DispId as a hint to order the
interface in a specific way? Or how does it do that?

Thanks,



[quoted text, click to view]
MSDNAndi
4/18/2006 5:21:01 AM
Peter,

I dug through a lot of info meanwhile.
Funny enough, the "order in the interface" seems not to be explicitly
documented anywhere.
However, I ordered my C# interface definition now in the exact same order as
it was in the IDL.
Something wonderful has happened, it worked.

(I changed GUID of DLL2 to something else for the reason that DLL2 still
needs to be called in the new C# DLL, so the new C# DLL replacing DLL2 and
the old DLL2 need to coexist).

Process I used:
Add reference to DLL2 in VS.
Create an interface inheriting from the interface that VS "offers" (through
interop).
Create a class implementing that interface.
Use the VS 2005 option to create an empty implementation of the interface.
Then... use VS 2005 option to "extract interface" for this implementation
to get source code for the interface definition.
This however, is ordered by alphabet....
Change the class to be an interface implementation of that new interface.
Reorder the methods in the C#-interface in the same order they were in the
c++-IDL. (The class implementation order does not matter).
And it works.

However, now I run into new troubles - some of the DLL2 return values are
different success HRESULT - but I still would like to catch failure-HRESULTS
automatically - and I need to propagate to DLL1 with the appropriate
values... but that is a different story (and will be a different post).

Regards,

MSDNAndi
4/18/2006 11:14:03 PM
Hi Peter,

thanks for sticking with me here :)
In case you have seen my finding in a documentation, please point me there.
If not - then it would be good, if someone would send a note to the MSDN
library people or to the people writing KB-articles suggesting to describe
this behaviour.

Regards,


v-phuang NO[at]SPAM online.microsoft.com (
4/19/2006 12:00:00 AM
Hi

I am glad that works for you.
Based on my test, I agree with your finding, the order in the interface
will decided the exposed order.

Also the DispID is used when we are using latebinding, which is what the
vbscript do.

Best regards,

Peter Huang

Microsoft Online Community Support
==================================================
When responding to posts, please "Reply to Group" via your newsreader so
that others may learn and benefit from your issue.
==================================================
This posting is provided "AS IS" with no warranties, and confers no rights.
v-phuang NO[at]SPAM online.microsoft.com (
4/21/2006 12:00:00 AM
Hi

So far I did not find document about that.
I think it should be a common sense that the result order goes the source
code order which is reasonable.
Anyway, thanks for your feedback.
I suggest you can submit this feedback to our product feedback center:
http://lab.msdn.microsoft.com/productfeedback/default.aspx

Best regards,

Peter Huang

Microsoft Online Community Support
==================================================
When responding to posts, please "Reply to Group" via your newsreader so
that others may learn and benefit from your issue.
==================================================
This posting is provided "AS IS" with no warranties, and confers no rights.
MSDNAndi
4/21/2006 2:58:02 AM
Hi,
I disagree on the "common sense" part since a compiler can well rearrange
things like method order easily. Furthermore in terms of where to put
declarations, C# usually is not aware of any order (inside the right block).
The documentation then has a gap here regarding COM-InterOp.
In my humble opinion if something is brought to the attention of a Microsoft
employee, they should forward that.
Even though I consider this topic very interesting, I unfortunately need to
to work on other issues now.

Thanks,


[quoted text, click to view]
v-phuang NO[at]SPAM online.microsoft.com (
4/24/2006 2:25:51 AM
Hi

Thanks for your quickly reply!
If you have any other concern, please feel free to post here.

Best regards,

Peter Huang

Microsoft Online Community Support
==================================================
When responding to posts, please "Reply to Group" via your newsreader so
that others may learn and benefit from your issue.
==================================================
This posting is provided "AS IS" with no warranties, and confers no rights.
AddThis Social Bookmark Button