Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

C# DLL with non .NET clients, getting ESP errors

15 views
Skip to first unread message

dev

unread,
Mar 6, 2012, 10:18:24 AM3/6/12
to
I have rewritten a COM DLL into C#, with a number of interface
functions, and a number of events that can be fired back to the
client(s). I can call it from a VB (6) client without problems.
However, when I try to use a C++ client (still visual studio 6) that
worked with the old DLL, any call to an interface function gets the
error "The value of ESP was not properly saved across a function
call."

A couple of example functions would be :
(C#)
int DialDigits([MarshalAs(UnmanagedType.BStr)]string
bsUserId,
[MarshalAs(UnmanagedType.BStr)]string szDigits);
int QueryStatusString([MarshalAs(UnmanagedType.BStr)] ref
string pbsStatus);
(C++)
m_pInterface->DialDigits(_bstr_t("1001").copy(),
_bstr_t("01234567890").copy());
BSTR bsError;
if ( m_pTapi->QueryStatusString( &bsError ) ==
S_OK ) ....
Both of these function calls get the same error.

The VB client doesn't seem to care whether the C# parameters are
marshalled or not, they just works, but I added the marshalling as
hinted by some other web site, but that didn't help in this C++ case.
Is there anything wrong with either my C#' interface definition, or
is
something special required in order to call a .NET COM dll from
unmanaged code ?

I can post the full interface if necessary, but it is a big lengthy.
I actually converted the IDL to C# by hand, but when it worked from
VB, I assumed I'd got it all right, but now I'm not so sure. Can
anyone help ?

I'm wondering if the issue is anything to do with the return type of
the
functions. In the old C++ DLL they were defined as STDMETHODIMP
(i.e. returning an HRESULT). What type should they therefore be in
C# ?

- Tim

Peter Duniho

unread,
Mar 6, 2012, 10:31:46 AM3/6/12
to
On Tue, 6 Mar 2012 07:18:24 -0800 (PST), dev wrote:

> I have rewritten a COM DLL into C#, with a number of interface
> functions, and a number of events that can be fired back to the
> client(s). I can call it from a VB (6) client without problems.
> However, when I try to use a C++ client (still visual studio 6) that
> worked with the old DLL, any call to an interface function gets the
> error "The value of ESP was not properly saved across a function
> call."

Sounds like a calling-convention mismtach.

> [...]
> I can post the full interface if necessary, but it is a big lengthy.

To post an effective question, you need to reproduce the problem with a
smaller example. Start with a single-member COM interface implemented in
C++ and perform the same conversion steps on it to produce the same results
but with a simple example.

> [...]
> I'm wondering if the issue is anything to do with the return type of
> the
> functions. In the old C++ DLL they were defined as STDMETHODIMP
> (i.e. returning an HRESULT). What type should they therefore be in
> C# ?

I haven't memorized the Windows include-file typedefs or the .NET defaults,
but judging from the name and the error, you might have to explicitly
declare your methods as "StdCall".

It's been a couple of years since I've done this, but my recollection is
that VS can import a typelib as a C# interface, providing you with exactly
the right declarations to use. Just start with a COM reference to the old
C++ COM server, and then use that as your starting point for your new C#
COM server. It's a lot easier than hand-porting the original IDL.

Of course, there is the question of whether a fresh rewrite is really the
most practical approach anyway. You can convert the original C++ code to a
mixed-mode C++/CLI DLL, which will allow the COM-facing part of the code to
stay the same (i.e. working :) ), but then also allow the implementation to
reference managed code.

There are still some hoops to jump through to get that to work as well, but
it does allow you to focus on individual parts of the problem at one time.

Pete

dev

unread,
Mar 7, 2012, 5:16:40 AM3/7/12
to
On Mar 6, 3:31 pm, Peter Duniho <NpOeStPe...@NnOwSlPiAnMk.com> wrote:
> On Tue, 6 Mar 2012 07:18:24 -0800 (PST), dev wrote:
> > ........

Thanks for the quick reply. I don't really want to rewrite/recreate my
interface if I don't have to,
as its mostly done, and seems to be working well with a VB client. I
am still hoping that
it just needs a minor 'tweak' to make it work with a C++ client. I
will see if I can create
a simple example to post, as you suggested.

In the meantime, does anyone how to generate C# code from a tlb file
(using tlbimp or another
tool)? Some people have mentioned it, but so far all I've managed to
generate is another DLL.

- Tim

Peter Duniho

unread,
Mar 7, 2012, 10:22:24 AM3/7/12
to
On Wed, 7 Mar 2012 02:16:40 -0800 (PST), dev wrote:

> [...]
> In the meantime, does anyone how to generate C# code from a tlb file
> (using tlbimp or another
> tool)? Some people have mentioned it, but so far all I've managed to
> generate is another DLL.

I don't recall if you can, at least in VS. I have a vague memory that
maybe there's a feature that supports that, but I'm not sure and don't
remember any details from when I was dealing with COM a couple of years
ago.

That said, you can decompile a managed assembly to C# and then use the
results to write a new .cs file. If you start with the DLL generated by
tlbimp.exe, that should produce useful results.

For that matter, even ildasm.exe can give you detailed information about
method declarations that you can use to rewrite them in C#.

Pete

Arne Vajhøj

unread,
Mar 8, 2012, 5:08:15 PM3/8/12
to
On 3/7/2012 5:16 AM, dev wrote:
> In the meantime, does anyone how to generate C# code from a tlb file
> (using tlbimp or another
> tool)? Some people have mentioned it, but so far all I've managed to
> generate is another DLL.

That is what you need.

:-)

The generated DLL is a .NET assembly which you just
add a ref to similar to any other .NET assembly.

And you can then you the types in the .NET assembly
in your C# code.

Arne


Arne Vajhøj

unread,
Mar 8, 2012, 5:13:40 PM3/8/12
to
Example.

VBS usage:

Set test = CreateObject("Test.CoTest")
WScript.Echo CStr(test.Add(123, 456))
WScript.Echo test.Concat("ABC", "XYZ")

C# usage with ref til DLL fra TLB import:

CoTest cotest = new CoTestClass();
ITest test = (ITest)cotest;
Console.WriteLine(test.Add(123, 456));
Console.WriteLine(test.Concat("ABC", "XYZ"));

C# usage without TLB import but instead using the C# 4.0 dynamic keyword:

dynamic test =
Activator.CreateInstance(Type.GetTypeFromProgID("Test.CoTest"));
Console.WriteLine(test.Add(123, 456));
Console.WriteLine(test.Concat("ABC", "XYZ"));

The examples are not completely equivalent as the C# via TLB import
does not go through IDispatch as the other two does.

Arne

Arne Vajhøj

unread,
Mar 8, 2012, 5:17:29 PM3/8/12
to
On 3/6/2012 10:18 AM, dev wrote:
> I have rewritten a COM DLL into C#, with a number of interface
> functions, and a number of events that can be fired back to the
> client(s). I can call it from a VB (6) client without problems.
> However, when I try to use a C++ client (still visual studio 6) that
> worked with the old DLL, any call to an interface function gets the
> error "The value of ESP was not properly saved across a function
> call."
>
> A couple of example functions would be :
> (C#)
> int DialDigits([MarshalAs(UnmanagedType.BStr)]string
> bsUserId,
> [MarshalAs(UnmanagedType.BStr)]string szDigits);
> int QueryStatusString([MarshalAs(UnmanagedType.BStr)] ref
> string pbsStatus);
> (C++)
> m_pInterface->DialDigits(_bstr_t("1001").copy(),
> _bstr_t("01234567890").copy());
> BSTR bsError;
> if ( m_pTapi->QueryStatusString(&bsError ) ==
> S_OK ) ....
> Both of these function calls get the same error.
>
> The VB client doesn't seem to care whether the C# parameters are
> marshalled or not, they just works, but I added the marshalling as
> hinted by some other web site, but that didn't help in this C++ case.
> Is there anything wrong with either my C#' interface definition, or
> is
> something special required in order to call a .NET COM dll from
> unmanaged code ?
>
> I can post the full interface if necessary, but it is a big lengthy.
> I actually converted the IDL to C# by hand, but when it worked from
> VB, I assumed I'd got it all right, but now I'm not so sure. Can
> anyone help ?
>
> I'm wondering if the issue is anything to do with the return type of
> the
> functions. In the old C++ DLL they were defined as STDMETHODIMP
> (i.e. returning an HRESULT). What type should they therefore be in
> C# ?

There is way too much going on behind the scene for .NET-COM
interop for manual conversion to be a good conversion.

TLB import or using dynamic are the ways to go in C#.

(dynamic requires C# 4.0 or higher)

See examples in other post.

Arne

dev

unread,
Mar 9, 2012, 3:31:59 AM3/9/12
to
Thanks for all the replies.

However, just to be clear, the C# here is the server, not a
replacement client.

How would a tlb import work in that situation ?

- Tim

Arne Vajhøj

unread,
Mar 12, 2012, 9:37:35 PM3/12/12
to
> However, just to be clear, the C# here is the server, not a
> replacement client.
>
> How would a tlb import work in that situation ?

Ah.

That is a complete different scenario.

Sorry for misunderstanding completely.

For server you can either:
a) write both interfaces and classes from scratch and try to add
all the necessary attributes.
b) tlbimp and just write the classes from scratch implementing the
generated interfaces

Again I will consider solution #a for unsuitable for more complex
cases. But if you go this route then you can use tlbexp to generate
a new tlb and use a tool to compare the content of old and the new tlb.

But option #b should be the way to go.

You may find the book "COM and .NET Interoperability"
by Andrew Troelsen very useful.

Chapter 10 covers #a above and chapter 12 covers #b above.

The book used to be available for download.

Arne



Arne Vajhøj

unread,
Mar 12, 2012, 9:38:54 PM3/12/12
to

dev

unread,
Mar 14, 2012, 5:49:01 AM3/14/12
to
Okay, the solution turned out to be fairly simple ...... after all
that.

It was that the #import needed to have a "no_namespace" option on it,
so that the definition matched that of the original COM server.

After a bit of fiddling with marshalling and types, I'm now calling my
COM
server functions correctly.

However, I'm still wasn't getting callbacks arriving, and I think I'll
post that
as a separate thread.

Many thanks to all those who responded.

- Tim
0 new messages