Exception details lost when thrown from C++ to C# through COM interop? - c#

I am consuming a cpp COM object from c# code.
My c# code looks like this:
try
{
var res = myComServer.GetSomething();
}
catch (Exception e) { }
However the exception never contains any of the details I set in cpp, in particular my error message.
In my cpp side I have followed several examples I have found on the web:
...
ICreateErrorInfo *pcerrinfo;
IErrorInfo *perrinfo;
HRESULT hr;
hr = CreateErrorInfo(&pcerrinfo);
pcerrinfo->SetDescription(L"C++ Exception");
hr = pcerrinfo->QueryInterface(IID_IErrorInfo, (LPVOID FAR*) &perrinfo);
if (SUCCEEDED(hr))
{
SetErrorInfo(0, perrinfo);
perrinfo->Release();
}
pcerrinfo->Release();
return E_FAIL; // E_FAIL or other appropriate failure code
...
Am I missing anything?
Is there anything else that could affect this, like marshaling, the interop creation or attributes of the com server itself?

Does your COM class support ISupportErrorInfo ?

Assuming that your class does implement ISupportErrorInfo, did you by any chance add the support AFTER you imported the library into your C# project from Visual Studio?
Visual Studio generates the gunk that it needs to talk to a COM library only once, when you import the library. For this purpose, it builds a special translation DLL called "originalDllName.Interop.dll", based on the information available in the TypeLib of the DLL at the time of the import.
You can make implementation changes as often as you want without trouble; but if you changed the library in any way (added new classes, changed the interface definitions, changed the iterfaces implemented by your classes...), you will have to remove the COM DLL from your References, and then re-import it, for the Interop DLL to be refreshed.

I was facing the exact same issue. I had implemented ISupportErrorInfo and the InterfaceSupportsErrorInfo method in my COM module.Still, In C# exception I was not getting the error description I had set in perrorinfo on the C++ side. In my case, the entry of COM_INTERFACE_ENTRY(ISupportErrorInfo) was missing in the header file.

Instead of catching the Exception type, catch COMException type like this ...
try
{
// COM call
}
catch( COMException cEx )
{
// Check HRESULT here
}

Related

C# COM object and CreateDispatch

First of all: There is existing Code using CreateDispatch. The maintainer doesn't want to change the code for compatibility/convenience reasons (except for using a new TLB/GUID).
So I have to create COM object which works with this restrictions. Preferabbly in C# (but C++ is also fine).
Problem is: I have absolutely no experience with COM.
That's how far I got: I created a COM object in C#, registered it and got a tlb. I checked the Registry, there is an entry: HKEY_CLASSES_ROOT\Wow6432Node\CLSID{36E6BC94-308C-4952-84E6-109041990EF7}
Seems fine. Next step: creating a test program (C++). I created a C++ console project with MFC enabled, imported the tlb. Then I added the following lines to the main:
CInterface01 server;
COleException* pe = new COleException;
LPTSTR m = new TCHAR[255];
CoInitialize(NULL);
server.CreateDispatch(L"{36E6BC94-308C-4952-84E6-109041990EF7}", pe);
pe->GetErrorMessage(m, 255);
Somehow the CreateDispatch didn't work. In the Exception it reads: "Class not registered"(what?! it's in the registry). Even worse: It crashes the Visual Studio when I'm running the same program again.
It feels like the solution is near, but I have no idea whats going wrong.
You need code like this:
//interface wrapper method implementations for
#import "YouTlbModule.tlb" no_namespace
//function
CoInitializeEx ( NULL, COINIT_MULTITHREADED);
IYouTlbModulePtr ptrYouTlbModule;
HRESULT hResult = ptrYouTlbModule.CreateInstance(OLESTR("Your.Component.Name"));
//test hResult
//Others function call
hResult = ptrYouTlbModule.Other(122, L"AAA");
//Call
int ret = ptrYouTlbInput.GetErrorMessage(m, 255);

Issue with COM References : System.Runtime.InteropServices.COMException: Creating an instance of the COM component with CLSID

I am a French intern and I got a really big issue so can you help me please :
The goal of my project is to automatize some tests from a .dll coded in VB6 in a C# programs.
In fact I use some classes from my VB project which will allow us to prevent regressions in the code. The .dll is in x86 so my C# project.
Here is an example of how I use the dll
using E2S_Equipment;
…
public void verifyEquipmentTextProperty(string eqpCode, bool equipmentIsDynamic, string textPropertyCode, bool textPropertyIsDynamic, string propValue)
{
//Class from E2S_Equipment dll
claEQPSRVReadString readStrService = new claEQPSRVReadString();
readStrService.LoadByKey(eqpCode);
…
}
All my test are in success when I launch them in Visual Studio but when I launch them with Command Line with MSTest the first test is in success and the others are in failure. My error is :
System.Runtime.InteropServices.COMException: Creating an instance of the COM component with CLSID {987C190C-8CFD-4E41-882B-3BAE73768066} from the IClassFactory failed due to the following error: 800a005b Exception from HRESULT: 0x800A005B.
My problem concern the declaration of claEQPSRVReadString
My first thought was that my code created for each test a new instance of the COM Object and so I created a Singleton pattern to have only one instance of my COM component like this:
public static class SrvReadTextPropertySingleton
{
private static claEQPSRVReadString mEqpSrvReadTextProperty;
public static claEQPSRVReadString EqpSrvReadTextProperty
{
get
{
if (mEqpSrvReadTextProperty == null)
{
mEqpSrvReadTextProperty = new claEQPSRVReadString();
}
return mEqpSrvReadTextProperty;
}
}
}
And now I get this error SrvReadTextPropertySingleton. EqpSrvReadTextProperty.LoadByKey(eqpCode);.
System.Runtime.InteropServices.InvalidComObjectException: COM object that has been separated from its underlying RCW cannot be used.
So, do you have any idea?
Thanks you in advance !
Are you 100% sure that your project settings are correct. Your Platform Target should be x86 and the Prefer 32-bit check box should be ticked. Also are you running the Debug version or the release version from the command line. You need to set your 32 bit settings for both environments.
For the command line version print out the following to ensure your environment is correct.
Console.WriteLine("OS {0}, Process {1}", System.Environment.Is64BitOperatingSystem, System.Environment.Is64BitProcess);
It may also be useful to keep a reference to the object you are using so that you create it before you use it and then assign it's output value.
var instance = new claEQPSRVReadString();
mEqpSrvReadTextProperty = instance.Value;
Once of course that's possible to access the value that was read from the instance. It looks like it's doing all it's work during the construction phase in the VB6 component, which is a some what an unusual way of doing it. Do you have any other methods to use on the instance that is created.
I Found the problem. It's was due link to the Thread Execution.
In the .testsettings file we add :
<Execution>
<ExecutionThread apartmentState="MTA"/>
</Execution>
</TestSettings>
http://ralessi.wordpress.com/2013/09/11/mta-testing-on-vs2012/

Calling C# from C++, difficulties with "." in C# namespace

First off, I'll admit I'm cargo-culting this a little bit -- my nice clean sample code doesn't work when I'm wedging it into the real world. That being said...
I have a DLL called CPierce.CSharpCall.dll that has something like the following C# in it:
namespace CPierce.CSharpBridge
{
[System.Runtime.InteropServices.Guid("3D08DF02-EFBA-4A65-AD84-B08ADEADBEEF")]
public interface ICSide
{
// interface definition omitted...
}
[System.Runtime.InteropServices.Guid("CBC04D81-398B-4B03-A3D1-C6D5DEADBEEF")]
public partial class CSide : ICSide
{
// class definition omitted...
}
}
This is registered with regasm /tlb, etc.. Then, my C++ code looks something like this:
#import "CPierce.CSharpCall.tlb" named_guids
// Contains syntax errors!
int myfunc()
{
HRESULT hRes = S_OK;
CoInitialize(NULL);
CPierce.CSharpBridge::ICSide *pManagedInterface = NULL;
hRes = CoCreateInstance(
CPierce.CSharpBridge::CLSID_Class1,
NULL, CLSCTX_INPROC_SERVER,
CPierce.CSharpBridge::ICSide,
reinterpret_cast<void**> (&pManagedInterface));
// Calls to the interface omitted....
CoUninitialize();
return 0;
}
The problem is, of course, the syntactically wrong bit about CPierce.CSharpBridge. I know in C++ if I want to have a similar namespace to the C# code I could say:
namespace CPierce
{
namespace CSharpBridge
{
// definitions go here
}
}
But I don't think that's what I'm looking for here, since I just need to refer to two constants that are in another namespace without putting the entire method in that namespace.
What is the C++ syntax I need to complete this call to CoCreateInstance?
Update: On deeper (much deeper) inspection, I'm finding that my .tlb file created by regasm is nearly empty. When I catenated all of my source into a single .cs file and compile with:
csc /debug /t:library BigFile.cs
regasm BigFile.dll /tlb:BigFile.tlb
I get a hefty (and useful) tlb file.
When I compile the whole project from Visual Studio, I'm getting a .DLL all right, but regasm doesn't do anything with it but produce a minimal .tlb file. (ildasm shows almost no differences between the two DLL's)
If I compile BigFile.cs in Visual Studio, I get a DLL that's also useless.
I'm stumped.
C++ doesn't use the . operator to delimit namespaces; it uses ::. You would use CPierce::CSharpBridge instead of CPierce.CSharpBridge. Unfortunately, that doesn't seem to help you because you don't know what namespace is actually being generated by the TLB.
A simple solution to that is to not use namespaces at all and import without them:
#import "CPierce.CSharpCall.tlb" named_guids no_namespace
This appears to be a problem on the C#/Visual Studio side. I'll abandon this question and open an appropriate one. Thank you for your help in narrowing it down.

TLI file throwing exception

I am using COM component in C++/CLI, one of the method of COM, takes 'void *' as parameter. My code compiles fine but throws 'System.Accessviolation' exception at runtime
following is the code snippet. What could be reason for this exception.
// C++ managed Code
void ManagedWrapper::InitializeConfig(ManagedConfigruation ^objConfiguration)
{
objConfiguration->SetConfigurationValue();
IntPtr p = objConfiguration->GetObjectPtr();
m_objCameraConfig->InitializeNetworkConfig(p.ToPointer());
}
//COM signature for InitializeNetworkConfig in IDL file
[helpstring("method InitializeCameraConfig")] HRESULT InitializeNetworkConfig([in] void *configparam);
How old is the COM object and do it need administrator priviliges? I know that some core functionality got the UAC leash on them when Windows moved from XP to Vista and 7.
It maybe totally wrong but hopefully that gives you a pointer in the right direction...

Installing OpenXML File Converter

I have written a managed OpenXML file converter in c#, but I'm having trouble with the deployment. For deployment, I am using a VS Setup Project.
I guess my first question is, I see some people using a Class Library and others using a Windows Application as the COM server. Is there a preference on either one? My converter has dependencies on libraries not in the GAC.
When it comes to registering the COM server, the following post: http://blogs.msdn.com/b/speront/archive/2009/04/17/9553717.aspx
suggests adding this to the Main() of a managed EXE:
Application.OleRequired();
MyConverter converter = new MyConverter();
Application.Run();
Which would not work for a setup project. This does work though if I manually run the EXE first.
I've tried running regasm:
regasm MyConverter.dll, which succeeds, but when Microsoft Word tries to use the converter, I get the error "Word cannot start the converter MyConverter Document"
Next, I tried creating a Windows Application and using:
public static void Main(string[] args)
{
Guid guid = new Guid("EFADDB5B-933E-49FE-B3C8-F6FD7FB1B788");
RegistrationServices regSrv = new RegistrationServices();
regSrv.RegisterTypeForComClients(typeof(MyConverter), ref guid);
}
Lastly, I tried:
regasm /regfile:test.reg MyConverter.dll
and then importing the registry file.
All of these give the error: "Word cannot start the converter MyConverter Document"
I have the correct registry entries for my converter in Office\12.0\Word\Text Converters\OOXML Converters\Import
The converter has successfully worked. It's just that deployment does not work under any instance.
If you set up the converter correctly it might be that it throws an unhandled exception when Word tries to start it. To figure out what the exception is it is probably a good idea to wrap all of your interface methods with a try/catch block and log the exception stack trace:
public void HrImport(
string bstrSourcePath,
string bstrDestPath,
IConverterApplicationPreferences pcap,
out IConverterPreferences ppcp,
IConverterUICallback pcuic)
{
try
{
// code to import document
}
catch (Exception ex)
{
// log the exception
//
System.Diagnostics.Trace(ex.ToString());
}
}

Categories

Resources