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();

No comments: