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);
Related
I am following the example in How to call a managed DLL from native Visual C++ code in Visual Studio.NET or in Visual Studio 2005 to call a .NET DLL from native C++ code. The code for C# looks like this:
public class StringExample: IStringExample
{
public string returnString(string input)
{
return input;
}
}
I followed the example's steps to build, register the COM assembly, and export the type library (.tlb) from the C# code.
In C++ code, I am trying to use code similar to the following:
#import "..\StringExample\bin\Debug\StringExample.tlb" raw_interfaces_only
using namespace StringExample;
void abc()
{
// Initialize COM.
HRESULT hr = CoInitialize(NULL);
// Create the interface pointer.
IStringExample ptr(__uuidof(StringExample));
BSTR bstrInput = L"hello world";
BSTR bstrOutput =L"";
ptr->returnString(bstrInput, &bstrOutput);
TCHAR* szOutput = (TCHAR *)_bstr_t(bstrOutput);
// Uninitialize COM.
CoUninitialize();
}
However, bstrOutput is empty. Moreover, I need to convert the bstrOutput to TCHAR* to pass it to a different api.
Is there an error in the variable initialization? Is there a different way to pass string variables between .NET and C++?
Function calls from Java to C# through JNI-C++/CLI are failing when the C# COM is not registered using regasm with the codebase option. I've built a sample following the instructions in P2: Calling C# from Java with some changes.
Numero uno: C#
Change the C# dll into a COM by creating an interface, IRunner, and making the library assembly COM-visible.
namespace RunnerCOM
{
public interface IRunner
{
String ping();
}
public class Runner:IRunner
{
static void Main(string[] args)
{
}
public Runner() { }
public String ping()
{
return "Alive (C#)";
}
}
}
Numero due: Java
No changes made to the Java section.
Numero tre: C++
This part was changed to create a new instance of the RunnerCOM.Runner class and use that result. Here is a good tutorial on how to call managed code from unmanaged code: http://support.microsoft.com/kb/828736
#include "stdafx.h"
#include "Runner.h"
#pragma once
#using <mscorlib.dll>
#import "RunnerCOM.tlb"
JNIEXPORT jstring JNICALL Java_Runner_ping(JNIEnv *env, jobject obj){
RunnerCOM::IRunnerPtr t = RunnerCOM::IRunnerPtr("RunnerCOM.Runner");
BSTR ping = t->ping();
_bstr_t temp(ping, true);
char cap[128];
for(unsigned int i=0;i<temp.length();i++){
cap[i] = (char)ping[i];
}
return env->NewStringUTF(cap);
}
Now to my questions,
The code above fails with a _com_error exception, Class not registered (0x80040154) unless the codebase option is enabled during regsitration of RunnerCOM.dll, with regasm.exe. Why is this? If the code is not ran from JNI, I tested it as an exe, it works fine. The RunnerCOM.dll is simply found in the working directory.
Type casting _bstr_t temp to char* fails. For example, char *out = (char*) temp; Similar to the issue above, it works fine when it's built and executed as an exe but crashes the JVM when it's a JNI call.
By the way this is what I used to run it as an executable:
int main(){
RunnerCOM::IRunnerPtr t = RunnerCOM::IRunnerPtr("RunnerCOM.Runner");
BSTR ping = t->ping();
_bstr_t temp(ping, false);
printf(temp);
return 0;
}
Codebase creates a Codebase entry in the registry. The Codebase entry specifies the file path for an assembly that is not installed in the global assembly cache, so when you specify the codebase, the system will find the DLL based on the path. If not, it will try to locate the dll in the GAC and current working directory. In JNI, I think the current working directory is not the folder where the DLL is. You can use process explorer to find what is the current working directory, also, you can use process monitor to find out which directories the exe is looking into to find the dll.
The code converting _bstr_t to char*, the char* string cap is not ended with '\0', I think this might cause problem in JNI. Uses the _bstr_t operator (char *), you can obtain a null terminated string from the _bstr_t object. Please check the msdn example for details.
You mentioned C++/CLI, C++/Cli and COM warpper are two different ways to interop with C# code. If you're using C++/CLI as a bridge, you doesn't need to register C# DLL as COM, please see this: Calling .Net Dlls from Java code without using regasm.
If you're using COM, you should call CoInitialize() to init COM first in your code.
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.
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...
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
}