C# COM object to TLB generation problem
-
I'm trying to access C# object as COM object in C++ and I generate TLB file and use #import in C++, but I'm having a bit of problems with return types. For example if have method: string GetValue() the generated interface (*.tlh file) gets translated to: virtual HRESULT __stdcall GetValue (/*[out,retval]*/ BSTR * pRetVal ) = 0; the return argument always gets put as last parameter. All the example I've seen (in books or MSDN) do not do that. They always return whatever is returned. Is there maybe some attribute I have to apply? I'm using VS2005, so maybe it's new behaviour there? Thanks.
-
I'm trying to access C# object as COM object in C++ and I generate TLB file and use #import in C++, but I'm having a bit of problems with return types. For example if have method: string GetValue() the generated interface (*.tlh file) gets translated to: virtual HRESULT __stdcall GetValue (/*[out,retval]*/ BSTR * pRetVal ) = 0; the return argument always gets put as last parameter. All the example I've seen (in books or MSDN) do not do that. They always return whatever is returned. Is there maybe some attribute I have to apply? I'm using VS2005, so maybe it's new behaviour there? Thanks.
This is the documented and expected behavior. All COM methods (and, by extension, properties) are supposed to return
HRESULT
s (as yours does), and what COM clients like VB and .NET via COM interop see is return values (although in .NET you can attribute your methods withPreserveSigAttribute
- among other ways - to make sure anHRESULT
is returned as anInt32
since the return result code might be important and may not be an error (likeS_FALSE
). I recomend that you read Creating a CCW for COM enabled non .NET applications[^] here on CodeProject, as well as Exposing .NET Framework Components to COM[^]. Make sure that you hard-code GUIDs with theGuidAttribute
and never change published interfaces (i.e., interfaces you've released unto the world). Always derive new ones. Also never use auto-generated class interfaces. Define your interfaces explicitly and implement your class interface as the first interface in your implementation list. Also, what examples are you looking at? I've read about every article and API doc in MSDN and COM methods are all returnHRESULT
s orSCODE
s (legacy) - it's part of the COM spec to do so. VB (pre-.NET) interprets[retval]
s as return values, just like tlbimp.exe and VS.NET do when importing a typelib. Other late-bound clients like Windows Script (VBScript, JScript) do so as well. This posting is provided "AS IS" with no warranties, and confers no rights. Software Design Engineer Developer Division Sustained Engineering Microsoft [My Articles] -
This is the documented and expected behavior. All COM methods (and, by extension, properties) are supposed to return
HRESULT
s (as yours does), and what COM clients like VB and .NET via COM interop see is return values (although in .NET you can attribute your methods withPreserveSigAttribute
- among other ways - to make sure anHRESULT
is returned as anInt32
since the return result code might be important and may not be an error (likeS_FALSE
). I recomend that you read Creating a CCW for COM enabled non .NET applications[^] here on CodeProject, as well as Exposing .NET Framework Components to COM[^]. Make sure that you hard-code GUIDs with theGuidAttribute
and never change published interfaces (i.e., interfaces you've released unto the world). Always derive new ones. Also never use auto-generated class interfaces. Define your interfaces explicitly and implement your class interface as the first interface in your implementation list. Also, what examples are you looking at? I've read about every article and API doc in MSDN and COM methods are all returnHRESULT
s orSCODE
s (legacy) - it's part of the COM spec to do so. VB (pre-.NET) interprets[retval]
s as return values, just like tlbimp.exe and VS.NET do when importing a typelib. Other late-bound clients like Windows Script (VBScript, JScript) do so as well. This posting is provided "AS IS" with no warranties, and confers no rights. Software Design Engineer Developer Division Sustained Engineering Microsoft [My Articles]Thanks for response. As usual Heath you provided excelent response. I saw in MSDN example (COM Interop Part 2: C# Server Tutorial) at http://msdn.microsoft.com/library/default.asp?url=/library/en-us/csref/html/vcwlkCOMInteropPart2CServerTutorial.asp?frame=true[^] where they have public int PrintHi(string name) method and call it with only one parameter (string) and use return int value.
-
Thanks for response. As usual Heath you provided excelent response. I saw in MSDN example (COM Interop Part 2: C# Server Tutorial) at http://msdn.microsoft.com/library/default.asp?url=/library/en-us/csref/html/vcwlkCOMInteropPart2CServerTutorial.asp?frame=true[^] where they have public int PrintHi(string name) method and call it with only one parameter (string) and use return int value.
You're actually supposed to use the
PreserveSigAttribute
when you do this. Some times the samples are incorrect. Try compiling the following:using System;
using System.Runtime.InteropServices;
[assembly: ComVisible(true)]
[assembly: Guid("519a4822-f224-47fa-bdc7-8037829365dc")]
[InterfaceType(ComInterfaceType.InterfaceIsDual)]
[Guid("f9b77b61-b4f0-4be4-9c53-ded9592e90c6")]
public interface ITest
{
[DispId(0)]
int Foo(string value);
}
[ClassInterface(ClassInterfaceType.None)]
[Guid("ae5e53c8-54d6-4667-9396-582f5c8611bf")]
public class Test
{
public int Foo(string value)
{
return value.Length;
}
}Compile using:
csc.exe /t:library test.cs
Next, register the library and generate a typelib:
regasm.exe /tlb test.dll
Finally, view the typelib:
oleview.exe test.tlb
You'll see the following:
// Generated .IDL file (by the OLE/COM Object Viewer)
//
// typelib filename: test.tlb
[
uuid(519A4822-F224-47FA-BDC7-8037829365DC),
version(1.0),
custom(90883F05-3D28-11D2-8F17-00A0C9A6186D, Test, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null)
]
library Test
{
// TLib : // TLib : mscorlib.dll : {BED7F4EA-1A96-11D2-8F08-00A0C9A6186D}
importlib("mscorlib.tlb");
// TLib : OLE Automation : {00020430-0000-0000-C000-000000000046}
importlib("stdole2.tlb");
// Forward declare all types defined in this typelib
interface ITest;
[
odl,
uuid(F9B77B61-B4F0-4BE4-9C53-DED9592E90C6),
version(1.0),
dual,
oleautomation,
custom(0F21F359-AB84-41E8-9A78-36D110E6D2F9, ITest)
]
interface ITest : IDispatch {
[id(00000000), propget,
custom(54FC8F55-38DE-4703-9C4E-250351302B1C, 1)]
HRESULT Foo(
[in] BSTR value,
[out, retval] long* pRetVal);
};
[
uuid(AE5E53C8-54D6-4667-9396-582F5C8611BF),
version(1.0),
custom(0F21F359-AB84-41E8-9A78-36D110E6D2F9, Test)
]
coclass Test {
[default] interface _Object;
};
};If you used
PreserveSigAttribute
, then it would simply beHRESULT Foo(BSTR value)
. This posting is provided "AS IS" with no warranties, and confers no rights. Software Design Engineer Developer Division Sustained Engineering Microsoft -
You're actually supposed to use the
PreserveSigAttribute
when you do this. Some times the samples are incorrect. Try compiling the following:using System;
using System.Runtime.InteropServices;
[assembly: ComVisible(true)]
[assembly: Guid("519a4822-f224-47fa-bdc7-8037829365dc")]
[InterfaceType(ComInterfaceType.InterfaceIsDual)]
[Guid("f9b77b61-b4f0-4be4-9c53-ded9592e90c6")]
public interface ITest
{
[DispId(0)]
int Foo(string value);
}
[ClassInterface(ClassInterfaceType.None)]
[Guid("ae5e53c8-54d6-4667-9396-582f5c8611bf")]
public class Test
{
public int Foo(string value)
{
return value.Length;
}
}Compile using:
csc.exe /t:library test.cs
Next, register the library and generate a typelib:
regasm.exe /tlb test.dll
Finally, view the typelib:
oleview.exe test.tlb
You'll see the following:
// Generated .IDL file (by the OLE/COM Object Viewer)
//
// typelib filename: test.tlb
[
uuid(519A4822-F224-47FA-BDC7-8037829365DC),
version(1.0),
custom(90883F05-3D28-11D2-8F17-00A0C9A6186D, Test, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null)
]
library Test
{
// TLib : // TLib : mscorlib.dll : {BED7F4EA-1A96-11D2-8F08-00A0C9A6186D}
importlib("mscorlib.tlb");
// TLib : OLE Automation : {00020430-0000-0000-C000-000000000046}
importlib("stdole2.tlb");
// Forward declare all types defined in this typelib
interface ITest;
[
odl,
uuid(F9B77B61-B4F0-4BE4-9C53-DED9592E90C6),
version(1.0),
dual,
oleautomation,
custom(0F21F359-AB84-41E8-9A78-36D110E6D2F9, ITest)
]
interface ITest : IDispatch {
[id(00000000), propget,
custom(54FC8F55-38DE-4703-9C4E-250351302B1C, 1)]
HRESULT Foo(
[in] BSTR value,
[out, retval] long* pRetVal);
};
[
uuid(AE5E53C8-54D6-4667-9396-582F5C8611BF),
version(1.0),
custom(0F21F359-AB84-41E8-9A78-36D110E6D2F9, Test)
]
coclass Test {
[default] interface _Object;
};
};If you used
PreserveSigAttribute
, then it would simply beHRESULT Foo(BSTR value)
. This posting is provided "AS IS" with no warranties, and confers no rights. Software Design Engineer Developer Division Sustained Engineering MicrosoftAlso, while we're on the subject. How do strings returned from .net work? If I have a method string getSomething() I have to call it as: HRESULT getSomething(BSTR* result); It seems I have to allocate the string for the result. I'm doing: CComBSTR result(20); and call it: getSomething(&result.m_str); what happens if I return string longer than 20 characters? Does CLR allocate more chars? I tried looking in MSDN, but I couldn't find any information. It seems all examples I can find only deal with ints or doubles.
-
Also, while we're on the subject. How do strings returned from .net work? If I have a method string getSomething() I have to call it as: HRESULT getSomething(BSTR* result); It seems I have to allocate the string for the result. I'm doing: CComBSTR result(20); and call it: getSomething(&result.m_str); what happens if I return string longer than 20 characters? Does CLR allocate more chars? I tried looking in MSDN, but I couldn't find any information. It seems all examples I can find only deal with ints or doubles.
It's an
[out, retval]
. Most often, you don't allocate these as the caller. Merely declare a variable and pass the address of the variable. Also, if you read the documentation for theCComBSTR
class, you'll see the following remarks:Memory Leak Issues
Passing the address of an initialized CComBSTR to a function as an [out] parameter causes a memory leak. In the example below, the string allocated to hold the string "Initialized" is leaked when the function OutString replaces the string. CComBSTR bstrLeak(L"Initialized"); HRESULT hr = OutString(&bstrLeak); To avoid the leak, call the Empty method on existing CComBSTR objects before passing the address as an [out] parameter. Note that the same code would not cause a leak if the function's parameter was [in, out].
So, as you see - don't initialize it. You also don't need to mess with
m_str
. Since theCComBSTR
contains only one member -m_str
- passing the address of theCComBSTR
instance is passing the address of them_str
(because of the way the class/struct is aligned). This posting is provided "AS IS" with no warranties, and confers no rights. Software Design Engineer Developer Division Sustained Engineering Microsoft [My Articles] -
It's an
[out, retval]
. Most often, you don't allocate these as the caller. Merely declare a variable and pass the address of the variable. Also, if you read the documentation for theCComBSTR
class, you'll see the following remarks:Memory Leak Issues
Passing the address of an initialized CComBSTR to a function as an [out] parameter causes a memory leak. In the example below, the string allocated to hold the string "Initialized" is leaked when the function OutString replaces the string. CComBSTR bstrLeak(L"Initialized"); HRESULT hr = OutString(&bstrLeak); To avoid the leak, call the Empty method on existing CComBSTR objects before passing the address as an [out] parameter. Note that the same code would not cause a leak if the function's parameter was [in, out].
So, as you see - don't initialize it. You also don't need to mess with
m_str
. Since theCComBSTR
contains only one member -m_str
- passing the address of theCComBSTR
instance is passing the address of them_str
(because of the way the class/struct is aligned). This posting is provided "AS IS" with no warranties, and confers no rights. Software Design Engineer Developer Division Sustained Engineering Microsoft [My Articles]I see. thanks. Do you know if there are any restrictions on types you can use within .net COM client? I have my C# class do: new SqlConnection(connectionstring) and it throws access violation exception: Error Msg: System.TypeInitializationException: The type initializer for 'System. Data.SqlClient.SqlConnectionFactory' threw an exception. ---> System.TypeInitial izationException: The type initializer for 'System.Data.SqlClient.SqlPerformance Counters' threw an exception. ---> System.AccessViolationException: Attempted to read or write protected memory. This is often an indication that other memory h as been corrupted. at System.Data.ProviderBase.DbConnectionPoolCounters.GetInstanceName() at System.Data.ProviderBase.DbConnectionPoolCounters.InitCounters(String cate goryName, String categoryHelp) at System.Data.SqlClient.SqlPerformanceCounters..ctor() at System.Data.SqlClient.SqlPerformanceCounters..cctor() --- End of inner exception stack trace --- at System.Data.SqlClient.SqlConnectionFactory..ctor() at System.Data.SqlClient.SqlConnectionFactory..cctor() --- End of inner exception stack trace --- at System.Data.SqlClient.SqlConnection..ctor() at System.Data.SqlClient.SqlConnection..ctor(String connectionString) When I use my client thru C#, it's all fine. My C++ COM clinet is very simple for the test case, it just creates COM object and calls one method with no parameters and no return values (I did that to see if it was some memory problem in C++ client).