Passing COM autoptr to managed C++ dll

D

Drew

I have recently converted all of my native C++ dll's to be compiled
with the /CLR switch. After doing this, I notcied a very strange and
frustrating issue.

One of my managed dll's calls another using a COM smartptr
(MSXML2::IXMLDOMDocumentPtr to be exact). When calling my function in
another managed DLL, my app starts throwing exceptions.

If I step into my function, before getting into my function, I see a
call to AddRef() and later a call to Release() on my smartptr. When I
am in the Release() call I see several levels of calls into the CLR
(mscorwks.dll). After this, the IP gets lost somewhere.

Now, if I step over my function, it comes back ok, but the smartptr is
messed up and when I try to use it, an exception is thrown.

The strange thing is, if I add the function that is in my separate
dll, into the same dll that is making the call, everything works fine.

I've tried reproducing this in a small test case, but it appears to
work. I don't know if I'm doing something wrong and it manages to work
by accident in the small test case or not. What is going on with the
COM object (I've even tried using a dump ptr- same problem)

I have gone over the "Converting Managed Extensions ... from Pure ...
to Mixed Mode". All my settings are set according to the document.
Here's a snippet of my code.

CDEGlobalEngine::TestFn()
{
MSXML2::IXMLDOMDocumentPtr pXmlDoc;
HRESULT hr = pXmlDoc.CreateInstance(CLSID_DOMDocument);

if (SUCCEEDED(hr))
{
// if i change my function to not pass in the pXmlDoc, everything
works fine
boolbReturnCode = CXTXml::LoadXml("options.xml", pXmlDoc);
}
}

Here's my code to init the CRT :

CDEGlobalEngine::CDEGlobalEngine()
{
HMODULE hDll = ::GetModuleHandle("otherDLL.dll");

if (hDll)
{
pfnEnsureInit pfnDll = (pfnEnsureInit) ::GetProcAddress(hDll,
"_DllEnsureInit@0");

if (pfnDll)
{
pfnDll();
}
}

// init COM for MSXML library
CoInitialize(NULL);
}
 
D

Drew Tennenbaum

I have figured out what was causing the problem, and I have a solution
although a terrible one...

My code was split up where my class method declarations are in a header file
and my class definitions are in a .cpp file.

When my code is in the .cpp file, I need to link to the .lib file. I add the
appropriate .lib file in my projects properties (Link->input).

If I make the code inline by moving it to the header file, I no longer have
to link, and everything works. This however will blow the size of my dll's
enormously.

So obviously this has something to do with a linking issue. This is how I
linked libraries in unmanaged C++, what's going on with the managed
libraries?

Thanks
 
D

Drew

Here's a small app that reproduces the problem. Hopefully someone
knows what is going on here. I'm hoping it's not a bug in VS 2003
compiler...

DLL project 1

Managed class (.h)
public __gc class CWrapper
{
public:
CWrapper() { }

void LoadOptions();
};

Managed class (.cpp)
void CWrapper::LoadOptions()
{
MSXML2::IXMLDOMDocumentPtr pDoc;
pDoc.CreateInstance(CLSID_DOMDocument);

::LoadXml("options.xml", pDoc); // declared/defined in header file
- works fine

CXml::Load("options.xml", pDoc); // declared in header/defined in
cpp file (after this call- things go wacky)

CXml::Load("options.xml", pDoc);
}


DLL project 2

(header file)
extern "C" __declspec(dllexport) void LoadXml(const std::string& file,
MSXML2::IXMLDOMDocumentPtr pDoc)
{
_variant_t varXml = file.c_str();
_variant_t varOut = pDoc->load(varXml);
}

Mixed class (header file)
class __declspec(dllexport) CXml
{
public:
CXml(void) { }
~CXml(void) { }

static bool Load(const std::string& filename,
MSXML2::IXMLDOMDocumentPtr pDoc);
};

Mixed class (cpp file)
bool CXml::Load(const std::string& filename,
MSXML2::IXMLDOMDocumentPtr pDoc)
{
_variant_t varXml = filename.c_str();
_variant_t varOut = pDoc->load(varXml);

return (true);
}

If I move the definition of CXml::Load into the header file and
comment out the CXml::Load in the cpp file, things work normally. Note
that if I do the same thing to the first call to "::LoadXml", the same
problem occurs. Of course when the code is in the cpp file, I have to
link specifically to the .lib file. When the code is in the header
file, a link is not necessary. I'm guessing the linking is where the
problem is occurring.
 
T

Tian Min Huang

Hello Drew,

Thanks for your post.

As I understand, you exported unmanaged class and function from DLL2 which
will be used in another DLL say, DLL1. You include the header files of DLL2
in DLL1. Please correct me if there is any misunderstanding. You are
correct that you will get linker errors if you neither link the .lib file
of DLL2 nor add the corresponding source code of DLL2 in DLL1.

If you want to link DLL2.lib to DLL1, please make sure that the DLL2
exported function or class should be imported to DLL1. That is, we should
change dllexport to dllimport of the header files of DLL2 and include them
to DLL1. For example,

Change
class __declspec(dllexport) CXml
To
class __declspec(dllimport) CXml

Please feel free to let me know if you have any problems or concerns. I
look forward to hearing from you.

Have a nice day!

Regards,

HuangTM
Microsoft Online Partner Support
MCSE/MCSD

Get Secure! -- www.microsoft.com/security
This posting is provided "as is" with no warranties and confers no rights.
 
D

Drew

Hi Tian- thanks for the response.

Even when I change the code to look like:

#ifdef MSXMLLIB2_EXPORTS
#define XML_API __declspec(dllexport)
#else
#define XML_API __declspec(dllimport)
#endif

class XML_API CXml

where MSXMLLIB2_EXPORTS is defined in my lib2 project, I still have to
manually link my app by placing msxmllib2.lib in the additional
dependencies box in my lib project (this isn't needed if I change my
project to not use managed extensions). Anyways, linking isn't really
my issue. It's what happens to the COM smart pointer after a call to
my lib2.

To illustrate my problem a slightly different way, here:

MSXML2::IXMLDOMDocumentPtr m_pDoc;
m_pDoc.CreateInstance(CLSID_DOMDocument);

// if LoadXml is defined in the .h the Release call works.
// if LoadXml is defined in the .cpp file, release causes an
exception
::LoadXml("options.xml", m_pDoc);

m_pDoc.Release(); // ignore the fact I shouldn't call release on a
smartptr.
// the real point is that m_pDoc is messed up and
causes
// an exception when I try and use it when
// LoadXml is defined in the .cpp file

Thanks.
Drew
 
T

Tian Min Huang

Hi Drew,

Thanks for your response. As I understand, the problem you are facing is
that a smart pointer passing to a DLL function will cause an exception.
Please correct me if there is any misunderstanding. I think more
information is needed before moving forward:

1. Could you please tell me the detailed exception message?

2. Please make sure that you import the same type library of MSXML on both
DLL and APP.

3. Do you link the same C run-time library to both DLL and APP? If not, it
may cause potential errors per KB article below:

PRB: Potential Errors Passing CRT Objects Across DLL Boundaries
http://support.microsoft.com/?id=190799

HOWTO: Link with the Correct C Run-Time (CRT) Library
http://support.microsoft.com/?id=140584

4. Is it possible for you to post simple projects which are able to
reproduce the problem? I will be glad to check it on my side.

I look forward to hearing from you.

Have a nice day!

Regards,

HuangTM
Microsoft Online Partner Support
MCSE/MCSD

Get Secure! -- www.microsoft.com/security
This posting is provided "as is" with no warranties and confers no rights.
 
D

Drew

I will post a simple application. I did last week, but my news server
must have lost it.


//MSXML2::IXMLDOMDocument* m_pDoc; // native ptr this works fine

MSXML2::IXMLDOMDocumentPtr m_pDoc; // after passing the smart ptr
// into the function, the ptr is
invalid
// try to use it after the fn,
and it
// crashes the CLR

m_pDoc.CreateInstance(CLSID_DOMDocument);

//CoCreateInstance(CLSID_DOMDocument, NULL,
// CLSCTX_INPROC_SERVER,
// IID_IXMLDOMDocument, (void**)&m_pDoc);

::LoadXml("options.xml", m_pDoc);

m_pDoc.Release();


..cpp of DLL
void LoadXml(const std::string& filename, MSXML2::IXMLDOMDocumentPtr
pDoc)
{
_variant_t varXml = filename.c_str();
_variant_t varOut = pDoc->load(varXml);
}


I am not linking with msvcrt.lib.

Thanks,
-Drew
 
T

Tian Min Huang

Hi Drew,

Thanks for your update. I noticed that the Release() call goes into CLR, so
I suggest you to explicitly declare IXMLDOMDocumentPtr as __nogc and then
check whether or not the problem still exists:

__nogc MSXML2::IXMLDOMDocumentPtr m_pDoc;

In the meantime, I am standing for your reproducible project.

Have a nice day!

Regards,

HuangTM
Microsoft Online Partner Support
MCSE/MCSD

Get Secure! -- www.microsoft.com/security
This posting is provided "as is" with no warranties and confers no rights.
 
D

Drew

I spent a day or so thinking that this had something to do with the
COM smartptr's. After wrapping MSXML's calls in my own library, I
noticed I still had the problem (I was no longer crossing DLL
boundries when calling a COM smartptr, so this confused me). I then
removed all SmartPtr's from my wrapped class and I am now using native
ptrs (MSXML2::IXMLDOMDocument*, etc.) I still had the problem.

I finally contacted MS tech support yesterday, and they have been able
to reproduce this and are currently looking into the problem.
Hopefully this gets resolved and if/when it does, I'll post a response
here.

-Drew
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top