Monday
May302005

sample p/invoke VS.NET 2003 solution

Microsoft includes a sample of language-interop via p/invoke with Visual Studio .NET 2003, but it's only set up as a makefile project. I do most of my Windows coding from inside VS.NET, so I wanted to learn how to create a dll from C++ code, then call it from C#, all from within the IDE. I built a VS.NET 2003 solution which builds the Microsoft sample code from within the IDE. Just posting it in the hope someone else might find it useful.

What I learned is that there's a very particular set of compiler options which must be set, and some very specific keywords/tags/attributes in both the C++ and the C-sharp code.

To set up the dll to have exported functionality:


  1. Set the compiler options so they look like this: /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_USRDLL" /D "PINVOKELIB_EXPORTS" /D "_WINDLL" /D "_MBCS" /FD /EHsc /RTC1 /MTd /Yc"stdafx.h" /Fp".\Debug/PinvokeLib.pch" /Fo".\Debug/" /Fd".\Debug/" /W3 /nologo /c /Gz /TP. To do this, edit the Property Pages for the project. Go through each of the C/C++ subcategories and set the options to get exactly this command line listed in the "Command Line" subcategory. skoolLIB.vcproj has these settings already, if you want to copy from there. This seems to do a whole bunch of things, including generating precompiled headers and a dll.

  2. Tag the declaration functions you wish to export with a macro -- this is too insane to describe; see the first comment in skoolLIB/PinvokeLib.h for details.

  3. Export the data structures:
    extern "C" PINVOKELIB_API int TestArrayOfStructs( MYPOINT* pPointArray, int size );
    typedef struct _MYPERSON
    {
    char* first;
    char* last;

To use the exported API from C#:


  1. Use the StructLayout attribute to describe the layout of datatypes to import:
    [ StructLayout( LayoutKind.Sequential, CharSet=CharSet.Ansi )]
    public struct MyPerson
    {
    public String first;
    public String last;
    }


  2. Declare the functions to import, and indicate where to find the library from which to load them:

    public class LibWrap
    {
    ....
    [ DllImport( "..\\skoolLIB\\Debug\\skoolLIB.dll") ]
    public static extern int TestStructInStruct( ref MyPerson2 person2 );
    ....
    }

  3. Call the imported functions as if they were normal functions:

    int res = LibWrap.TestStructInStruct( ref personAll );

Compiling the c# files doesn't need any special options, because the complicated part -- finding and loading the DLL -- is done at runtime. You've compiled a relative path into your code, though, which will probably complicate distribution.

I'm interested if anyone has insight into this process, or can point me at better documentation than what I've just written. There's way too much documentation; I guess I've just compounded the problem.

Friday
May272005

oh, the joy won't stop.

I was getting somewhere with the plan I laid out in my last post... except I couldn't get my C# code to call a function exported in the sample unmanaged C++ DLL created by VS.NET; I'd get a System.DllNotFoundException when I tried to call the function. I tried putting the dll into various places where Windows is supposed to search for dll's, but not dice. I decide to restart the computer -- a nice soft reset, quit everything nicely.

When the system comes back up, I launch the VS.NET solution file with all the various projects I've been working on... and VS.NET hangs. Okay, I launch it again... and VS.NET hangs again. I launch it again... and then give up. I didn't need that solution anyways.

I decide to find a complete working example of just the part that's troubling me, calling unmanaged functions from managed code... The example I find is actually already on my computer, in C:\Program Files\Microsoft Visual Studio .NET 2003\SDK\v1.1\Samples\Technologies\Interop\PlatformInvoke\Custom\. Why didn't I look there before? This sample is set up to build directly from the command line, with makefiles. Makefiles! Lovely! With a makefile, I can directly change command line options, and ellide all but the vital ones. Amazingly, all it takes to make a dll is cl.exe /clr /LD MyClass.cpp... Poof, there's MyClass.dll. Amazing. This one little example seems to have everything I need. Too bad it's almost midnight. Every day brings its own surprises.

Friday
May272005

progress with windows and opengl... towards a solution!

SharpGL works, and I'm learning about how to call unmanaged C++ from managed C#. It's all about loading libraries and those windows-specific tags. First there's a class, Imports, whose job is specifying and importing all the dll's, and establishing the structure of a few important types, such as PixelFormat. With that accomplished, a class called SharpGL.SceneGraph.OpenGL imports most of the OpenGL functions, and then wraps them with a public interface.

This line, from SharpGL...


public class OpenGL : Imports {
....
[DllImport(LIBRARY_OPENGL)] protected static extern void glVertex3f (float x, float y, float z);
...
}

...means that I can do this, in C#:
 
OpenGL.glVertex3f(x, y, z);

...but that's a protected function, so what I really need to do to call this code from another package is

myOpenGL.Vertex(x, y, z);

This is good, this is progress, but I don't want to write a molecule renderer in straight OpenGL. SharpGL provides a .NET component with a scene graph and lighting controls and camera controls and built-in shape classes, which is very helpful... but I want to use G3D, not SharpGL. G3D exists, and I know it, and I know its maintainers, and I know it is fast, and I know it works. Corey Taylor (one of the maintainers) says he'll try to help, but he's under a crazy workload... so I'm going to keep trying to do this myself.

My current approach:



  1. Keep my current C# class, MolecularModel, as the data structure for describing molecules. Unmanaged C++ can access managed, public C# classes.


  2. Write a small, unmanaged C++ class which renders a MolecularModel to an offscreen bitmap using G3D. Tag just the public interface of this class as dllexports.


  3. Write a small managed C# class which wraps my unmanaged C++ molecule renderer, by dll-importing the public interface which I exported in step 2. This managed class will not be a control; it's just a wrapper class. This wrapper's fundamental functionality will be to render a MolecularModel into a Bitmap.

  4. Replace the internals of the current ChemPad molecule viewer control, which is a .NET component, with calls to the managed molecule renderer wrapper. On Paint events, draw the Bitmap created by G3D onto this control's Graphics. Now instead of jmol-net generating the 3D image, G3D will be generating the 3D image.

This approach is different from my previous attempts in that it adds a layer which exposes a small API from unmanaged C++ to managed code. The compiler options will still be fairly hellish, and this isn't as good as really making all of G3D accessible from C#... but it keeps a very clean boundary between the chemistry part of the application and the rendering part of the application. Yikes. Wish me luck.

Friday
May272005

Windows Programming Sucks

Now that I've made my decision about where to work, I can finally come out and say it: Windows Programming Sucks! It's okay when everything is packaged up nicely, or when there are step-by-step instructions or examples for exactly what I need to do... but when I'm trying to do something just a bit odd, it's hopeless. Listen, I can RTFM with the best of them, but with these giant Microsoft products, RTFM ends up referring to dozens of msdn articles, book chapters, forum posts, and worst of all, tiny "help" strings (as if) in a multitude of property inspectors. Those articles etc depend on a deep knowledge of how Windows programming worked in the last two or three iterations: COM vs COM+ vs ATL vs MFC vs OMFG!. I swear to god, the solution to my problem seems to be "Just use P/Invoke." Then it turns out that P/Invoke is actually pinvoke and it's something that gets called by IL (Intermediate Language) which is generated by the C++ compiler. So "using P/Invoke" actually means something like "In these six dialog boxes, set these nine options to these values: '/Gz', 'Native', 'Never', 'Yes', and 'Only on Tuesdays.'" Except the documentation doesn't say so directly.

So why on earth am I using Windows, anyways? I'm working on a project which makes heavy use of the Tablet PC digitizer and Microsoft Handwriting Recognition. No other platform delivers these capabilities. The Tablet PC digitizer, when used with the Microsoft.Ink api's, generates an order of magnitude more samples of stylus location than external Wacom tablets, or any other device I've used. This level of detail yields much better handwriting recognition results. So, yes, thank you for that, Microsoft Tablet PC team.

My task for the last few days has been trying to switch renderers for this application, from a pure-C#, non-hardware-accelerated, non-standard chemistry renderer, to an OpenGL renderer, specifically, G3D. G3D is a multi-platform OpenGL abstraction written in C++. It's sweet, fast, well-documented, and I've been using it for about a year for various projects on Windows and Linux. So I want to use the G3D OpenGL renderer in my existing C# .NET application. In other words, I want to use a legacy library to enhance my .NET application; surely this is a common situation. Turns out that it's such a common situation that there are at least four ways to do it, and none of them are explained clearly, anywhere.

The best two options seem to be making a managed C++ wrapper which calls the "unmanaged" G3D code, or recompiling G3D with the "/clr" flag, which means, compile it to intermediate language, to run on the common language runtime. Sounds good, but the documentation for the first option is all about calling code in an existing DLL, or dynamically loaded library. G3D only exists as a statically loaded library. In Windows, a variety of insane keywords (__cdeclspec dllimport!) are required to tag any parts of an API which should be available to users of a DLL; those keywords are not required for users of a statically-linked library. G3D doesn't have those keywords, and so G3D doesn't make a useful dll. I haven't found documentation on calling statically-linked unmanaged C++ code from managed C++.

Recompililng G3D with the /clr flag sounds good, except that it just pushes the problem off by one step; G3D statically links with other unmanaged C++ libraries, like libpng and libjpeg. So I'd actually have to compile all of these satellite libraries with /clr, too. I haven't tried this yet, but I have a feeling that those libraries will try to do things which aren't permitted in managed C++; they're tuned for fast image manipulation. Even leaving out that worry, adding /clr to the compiler options for the G3D code triggers a cascade of incompatible compiler flags: run-time checking, exceptions, and certain kinds of optimizations, don't work with the /clr option. To turn off those incompatible compiler flags, though, requires the use of, I kid you not, three levels of hierarchical UI: per-project, per-category, and per-subcategory, with a dozen options for each subcategory. The subcategory options have names that don't necessarily correspond to the flag they modify; ie, to get the /clr flag, I set "Use managed C++ extensions" to "Yes," and to turn off /Gz I actually have to set two separate run-time type checking properties to some unknown pair of values. In fact, this is where I'm currently hung up; I have to turn off /Gz to compile with /clr, but I can't figure out how to turn off /Gz. The options in the three-deep UI for setting one of the two run-time type checking flags are something like "Auto," "Default," and "Yes," but what I need is "No."

I'm disheartened. None of the approaches I try quite exactly fail, they just descend into a combinatorial explosion of options and an ever-widening mass of documentation.

My remaining options:


  • using an existing C# OpenGL library: Tao, which is part of Mono, which is a whole 'nother barrel of fish; it seems to include its own compiler, and so I have no idea whether the Microsoft.Ink stuff will work with it. I dobut it.

  • using another existing C# OpenGL library: SharpGL. This seems like it might work, but the license looks very casual, and I got into this whole mess because I wasn't paranoid enough about the Java Research License.

  • Switching my application to be mostly unmanaged C++, with the Ink stuff in a managed C++ component. This is probably the most straightforward architecturally, but a) it requires rewriting a fair amount of code just before I turn maintenance over to a colleague and leave this job, b) it seems like an admission of defeat, and backwards, and c) it just might send me into the aforementioned dll hell.

I have to complete this task before I can leave this job and move to California. My frustration with Windows programming is unbounded.

Wednesday
May252005

Done!

Yep. I'm going to Laszlo. Hooray!