.NET Forum / .NET Framework / Interop / April 2006
C++ dll1 calls c#-wrapper-dll calls C++ dll2
|
|
Thread rating:  |
MSDNAndi - 13 Apr 2006 17:08 GMT 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 - 13 Apr 2006 17:17 GMT > I gave DLL2 completely new GUIDs. > Then I made a C# wrapper like this: [quoted text clipped - 4 lines] > MyDBLib.IDatabaseAccessLayer > public interface IDatabaseAccessLayerWrapper // before it had a :
> [ComVisible(true)] > [Guid("8D4B28E8-C132-4427-9ED6-B4BFC33C33CA")] > [ClassInterface(ClassInterfaceType.AutoDual)] //tried the other Options > [ProgId("MyDB.DatabaseAccessLayer.1")] > public class DatabaseAccessLayerWrapper : IDatabaseAccessLayerWrapper > { I should have mentioned that those Guids you see here are the Guids of the original Dll (from the .rgs and .idl).
Cheers,
"Peter Huang" [MSFT] - 14 Apr 2006 09:05 GMT 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 - 14 Apr 2006 12:20 GMT 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,
> Hi > [quoted text clipped - 24 lines] > ================================================== > This posting is provided "AS IS" with no warranties, and confers no rights. "Peter Huang" [MSFT] - 17 Apr 2006 04:04 GMT 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 - 17 Apr 2006 09:08 GMT 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,...) {... }
"Peter Huang" [MSFT] - 18 Apr 2006 08:14 GMT 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 - 18 Apr 2006 08:59 GMT 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,
> Dear Customer, > [quoted text clipped - 118 lines] > ================================================== > This posting is provided "AS IS" with no warranties, and confers no rights. MSDNAndi - 18 Apr 2006 13:21 GMT 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,
Andi
"Peter Huang" [MSFT] - 19 Apr 2006 04:36 GMT 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.
MSDNAndi - 19 Apr 2006 07:14 GMT 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,
"Peter Huang" [MSFT] - 21 Apr 2006 08:53 GMT 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 - 21 Apr 2006 10:58 GMT 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,
> Hi > [quoted text clipped - 15 lines] > ================================================== > This posting is provided "AS IS" with no warranties, and confers no rights. "Peter Huang" [MSFT] - 24 Apr 2006 03:25 GMT 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.
Free MagazinesGet these publications absolutely FREE for up to 12 months. There are no hidden fees and no obligation. Simply choose a title, complete the application form and submit it. Read more ...
|
|
|