Monday, July 12, 2010

c++ dll error when using MD[d]

It is possible for any programmer, even one that has been in the "trade" for a loooong time, to be an absolute idiot - especially when they are learning something new. Not always does hard learned skills help when trying something new.

Take my latest gaff as an example. Now I've been coding C++/cli mixed mode dlls since 2005, but I'd taken some time off right before Windows 7 arrived. Well, it was time to get back into the saddle, so I started work on a long dormant project and got it to a Beta state.

I rolled out the debug dll to my testers and when they tested it on Windows 7, Boom!.

Ok, time for some research .... lots of time for research.

Well, I finally found - that in a non-/cli C++ dll, I could use the /MT compiler switch and it would work just fine on XP and Win7. But if I used the /MD compiler switch, which is required to use C++/cli, it would still run on a clean XP install, but not on a clean Win7 install.

Arrrgggg!

After many posts on many different forums, I decided to try making a install package (which I've NEVER had to do before - its just a dll after all!) but Visual Studio pulled in two additional files, that it said were needed to run.

Well - Success!! Sort of. But, I happily posted my results on the various forums. One reply (Thanks Peter) pointed out that they were Debug files and that it may be a licence issue if I were to disseminate them.

That got me to wondering if this was all a Debug/Release mode issue.

Well, it was. I started with a new copy of my Win7 Virtual PC that also installs the C++ runtime files.

I then pushed a debug version to it just to verify that we still had the issue. Yep.

Then I pushed a release version to it and viola! It works. Dumb, dumb, dumb!

Why do I make things so hard for myself?

Wednesday, July 7, 2010

C++/cli -> calling c# dll -> calling OpenFileDialog issue

I write extensions to a Cartography program called Campaign Cartographer 3 (CC3 for short). It is sold by one of the most involved companies I have ever had the pleasure to be associated with ProFantasy.

To program an extension to CC3, you need to be able to write old school windows dlls. You remember the ones! They have a DllMain function? I saw that shudder, you do remember.

Well, whenever I can I use C++/cli (the .net version of C++) and if the extension is very complicated, I do most of my coding in C# and reference the C# dll from C++/cli. You can mix and match plain old C++ and C++/cli (and C#) through IJW (Believe it or not, that stands for "It Just Works"). As a matter of fact all my newer extensions hook into C++/cli, even if I do not use it - I want it there just in case.

Well, in my latest project, a self-contained wiki-like document reader/writer (hard to explain - maybe in a later post), I needed to call the standard .net openFileDialog and saveDileDialog controls.

All of a sudden, I'm getting COM errors!?!?

'System.Threading.ThreadStateException' occurred in System.Windows.Forms.dll

Additional information: Current thread must be set to single thread apartment (STA) mode before OLE calls can be made. Ensure that your Main function has STAThreadAttribute marked on it. This exception is only raised if a debugger is attached to the process.


OLE calls? How old an error is that!?

Well, it seems that if you try to call the .net file dialogs from C++/cli you need to do it from a STA Thread. But, since I cannot access the main thread, remember this is someone elses application - all I have access to is my DllMain. Well, after searching around and asking about how to deal with this error on StackOverflow.com - I got my answer, sort of.

The helpful people over at StackOverflow pointed out that I needed to start my own thead as an STAThread. They also gave me advice on how to do this via COM.

Well, I'm not adverse to admit that the amount of COM knowledge I have could be held in a teacup. But the Thread advice solved the problem. I just did it the C++/cli way!

First you need to isolate the code you want to run into a class.

ref class StaClass
{
    void CallWiki()
    {
        WikiNotes::FrmWiki fw;
        fw.ShowDialog();
    }
}


Then from my main C++/cli code, I just create a new thread and call "CallWiki"

StaClass wiki = gcnew StaClass;

ThreadStart^ threadDelegate = gcnew ThreadStart(wiki, &StaClass::CallWiki);
Thread^ newThread = gcnew Thread(threadDelegate, 0);
newThread->SetApartmentState(ApartmentState::STA);
newThread->Start();


Remember though, if you want to wait until your new thread finishes, like I did, then you can:

while (!workerThread->IsAlive);
regularThread->Join();