conceptual inertia

mediocrity at its finest

novo

without comments

Novo1.zip – “Lesson 101 – Box on a Plane” is the first sample in the PhysX SDK Training Programs. Here’s a screenshot of a DirectX port written in C++:

The first thing to do is download the PhysX SDK. After it’s installed, you’ll need to set the directory paths in Visual Studio for the include files:

The demo has standard first-person camera controls. Also, be sure to check out WM_KEYDOWN in the WindowProc for a list of other controls.

Novo2.zip – Lesson 102 – Sphere and Torque:

Novo3.zip – Lesson 103 – Capsule, Local Pose, and Center of Mass:

Novo4.zip – Lesson 104 – Convex Shapes and Anisotropic Friction:

Novo5.zip – Lesson 105 – The User Contact Report and the Contact Stream Iterator:

Novo6.zip – Lesson 106 – Static and Kinematic Actors. The screenshot doesn”t look like much, however, the user can control the middle cube (as a kinematic actor) bouncing it off the other actors.

Written by admin

June 17th, 2006 at 6:44 am

Posted in code

file type associations

without comments

Applications typically have some type of project file format. For example, Maya saves scenes using a binary file:

This file type is associated with Maya, it has the .mb extension and a custom icon. Double-clicking the file will open it in Maya. Today, we’re going to associate TestApp with the .testapp extension:

To associate a file type, you need to do three things:

  1. Add the file extension and associated application to the registry.
  2. When double-clicking a .testapp file, determine whether an instance of the associated process is already running.
  3. If the application is already running, pass the file across process boundaries.

The first step is easy and makes use of the following Win32 functions to create and set entries:

  • RegOpenKeyEx
  • RegCreateKeyEx
  • RegSetValueEx

The code to create key/value pairs in the registry is straightforward. Therefore, I’m not going to list it here so please reference the source code included in today’s download.

After executing the registration code, you should have new keys under HKEY_CLASSES_ROOT with the following structure:

And a new TestApp key where we assoicate the file with a default icon (which is indexed at 0 as an embedded resource in the executable):

The executable to launch is set under the Command section, with %1 being the command line param:

Use RegEdit to verify the above entries after executing today’s demo. To execute RegEdit use the Run prompt:

Once the file associations have been added to the registry, the next thing to do is determine if an instance of TestApp is running. The Process class in the System::Diagnostics namespace makes this step trivial.

So how does one application communicate with another across process boundaries? Under Win32 you use named pipes. A pipe connects two ends together – a client and server. Under .NET, named pipes have been wrapped up in the System::Runtime::Remoting::Channels::Ipc namespace. An IpcChannel is used when one application needs to communicate with another application in a different process on the same machine.

However, before we look at inter-process communication (IPC) let’s have a look at class SimpleFile which represents our .testapp file format.


public ref class SimpleFile : public MarshalByRefObject
{
    public: delegate void LoadDelegate(SimpleFile^ simpleFile);
    private: LoadDelegate^ loadDel;

    public: event LoadDelegate^ LoadEvent
    {
        void add(LoadDelegate^ value)
        {
            loadDel += value;
        }

        void remove(LoadDelegate^ value)
        {
           loadDel -= value;
        }
    }
 

Remoting is used for communication between app domains. An app domain is a logical container for a .NET applciation and is often refered to as a “lightweight process”.

The same remoting mechanism is used between two app domains whether those app domains are in the same process, in different processes on the same machine, or different processes on different machines. Any type that wants to expose its availability across app domains must inherit from MarshalByRefObject or be serializable.

To keep things as simple as possible, the SimpleFile “format” only contains a single value, which is parsed and stored:


private: int ultimateAnswer;
public: property int TheAnswerToTheUltimateQuestionOfLifeTheUniverseAndEverything
{
    int get() { return this->ultimateAnswer; }
}

public: void Load(String^ filePath)
{
    XmlTextReader^ xtr = gcnew XmlTextReader(filePath);

    while( xtr->Read() )
    {
        if( xtr->NodeType == XmlNodeType::Element )
        {
            if( xtr->Name == "UltimateAnswer" )
            {
                xtr->Read();
                this->ultimateAnswer = int::Parse(xtr->Value);
            }
       }
    }

    xtr->Close();
    delete xtr;

    if (loadDel != nullptr)
        loadDel(this);
}
 

Here we see the value stored in the sample file foobar.testapp:

The constructor in the Form class is overloaded to accept an array of arguments:


public: Form1(array<String^>^ args)
{
    InitializeComponent();

    // - in this example, we register the file type associations every time
    //   the application runs (overwriting keys)
    // - however, you would normally do this only once as part of your
    //   installation process
    RegisterFileAssociations();

    // - cache any arguments passed from Main
    this->args = args;
}
 

If there were no arguments passed, the application is starting up for the first time.


private: System::Void OnFormLoad(System::Object^ sender,
                                 System::EventArgs^ e)
{
    // - application is loading for the first time
    if( args->Length == 0 )
    {
        RegisterService();
    }
 

The host or server has to tell .NET which object’s it’s willing to expose. These objects or well-known types are registered using the static method RegisterWellKnownServiceType of the RemotingConfiguration class.

The host also has to register a channel with ChannelServices. A channel will monitor a port and accept incoming calls, servicing those calls with a thread from the thread pool. The IpcChannel is a welcome addition because prior versions of .NET provided only the TcpChannel and HttpChannel for communication. Both TcpChannel and HttpChannel have associated overhead for the network stack regardless if the communication taking place is on the same machine or across a network. IPC provides a direct line, reducing overhead and increasing performance.


private: void RegisterService()
{
    BinaryServerFormatterSinkProvider^ serverProv = 
        gcnew BinaryServerFormatterSinkProvider();
    serverProv->TypeFilterLevel = TypeFilterLevel::Full;

    Hashtable^ properties = gcnew Hashtable();
    properties["portName"] = "SimpleFileServer";

    IpcChannel^ ipc = gcnew IpcChannel(properties, nullptr, serverProv);
    ChannelServices::RegisterChannel(ipc, false);

    RemotingConfiguration::RegisterWellKnownServiceType(
                                      SimpleFile::typeid,
                                      "SimpleFile.rem",
                                      WellKnownObjectMode::Singleton);

    file = (SimpleFile^)Activator::GetObject(
                                      SimpleFile::typeid,
                                      "ipc://SimpleFileServer/SimpleFile.rem");

    file->LoadEvent += gcnew SimpleFile::LoadDelegate(this,
                                                      &Form1::OnProjectFileOpen);
}
 

During server registration, a SimpleFile is activated and its LoadEvent is hooked up to the following:


private: delegate void InvokeDelegate(SimpleFile^ simpleFile);
private: void OnProjectFileOpen(SimpleFile^ simpleFile)
{
    // cross-thread op
    array<Object^>^ arr = { simpleFile };
    this->Invoke(gcnew InvokeDelegate(this, &Form1::ProcessFile), arr);
}

public: void ProcessFile(SimpleFile^ simpleFile)
{
    this->label2->Text =
        simpleFile->TheAnswerToTheUltimateQuestionOfLifeTheUniverseAndEverything.ToString();
}
 

Invoke is used to update the Form’s user interface. Execution has to take place on the caller’s thread to avoid a cross-thread exception.

Back to OnFormLoad, where a list of processes is then assembled. It’s possible the process was launched with a double-click on a .testapp file. If so, services need to be registered and a SimpleFile loaded:


array<Process^>^ processArray =
    Process::GetProcessesByName(Process::GetCurrentProcess()->ProcessName);

// application loading for the first time from a double-click on file
if( processArray->Length == 1 )
{
    RegisterService();

    SimpleFile sf;
    sf.LoadEvent += gcnew SimpleFile::LoadDelegate(this,
                                                   &Form1::OnProjectFileOpen);
    sf.Load(filePath.ToString());
}
 

Otherwise, a process is already running and SimpleFile needs to get its information across process boundaries. Here we see the client registering an IpcChannel and activating the well-known file (SimpleFile):


else if( processArray->Length > 1 )
{
    MessageBox::Show("Instance already running, passing args along");

    for each( Process^ p in processArray )
    {
        if( p->Id != Process::GetCurrentProcess()->Id )
        {
            try
            {
                IpcChannel^ ipc = gcnew IpcChannel("SimpleClient");
                ChannelServices::RegisterChannel(ipc, false);

                SimpleFile^ file =
                    (SimpleFile^)Activator::GetObject(
                                        SimpleFile::typeid,
                                        "ipc://SimpleFileServer/SimpleFile.rem");

                if (file == nullptr)
                {
                    MessageBox::Show("Failure to connect with remote server");
                    this->Close();
                }

                file->Load(args[0]);
                this->Close();
            }
            catch(Exception^ e)
            {
                MessageBox::Show(e->Message);
            }

        } // end if

   } // end for each

} // end else if
 

In summary, the application is now assoicated with a file type. Double-clicking the file will launch the app. If the app is already running, IPC will be used to pass the file info across process boundaries, allowing the application already running to open the file as a new “project”.

Build and run the demo once to associate the file type in the registry. If you don’t see a blue icon for “foobar.testapp”, hit F5 in the explorer window to refresh it. Shut down the application and then try double-clicking various files with the .testapp extension.

You can download the source here.

Written by admin

June 15th, 2006 at 7:41 am

Posted in code

broken threads

without comments

I broke my robots arm today. How? I set him down on his belly with the power on. He was in a one arm push-up for a moment before I toggled the power off. Apparently there was enough weight on the servo to break two threads on a cog wheel. Each cog wheel is made of plastic, not metal.

I’ll be evaluating internal components for practical load requirements with my next purchase – lesson learned.

Written by admin

June 13th, 2006 at 6:01 pm

Posted in blather

flash splash

without comments

Ideally, your application should not need a splash screen because its load time should be nearly instantaneous. However, it’s not always the case. Updating the user about the progress of initialization is desirable. The ability to entertain the user at the same time is even more desirable. Most splash screens are static bitmaps. Some applications echo textual updates about which modules have been loaded or what systems have been initialized. This text typically flies by very quickly on-screen and may be echoed to a log file. What happens if we use Flash as the basis for a splash screen?

Today’s Solution consists of a C++/CLI class library containing a Flash splash screen class. A C# Windows application is used to test splash screen functionality. Please note that simply copying an existing Form (ie. the FlashForm in today’s download) between projects is not recommended because you’ll run into a lot of errors. Ultimately, it”s faster if you create the Form in the target project and then copy ‘n paste portions of the code over (rebuilding along the way).

Instructions on how to setup a ShockwaveFlash control can be found here.
The code for using Flash as a splash screen (including the .fla) is available for download here

Written by admin

June 6th, 2006 at 6:15 am

Posted in code

hosting flash

without comments

I was working on a project a while back that required a fairly rich UI. I was brought a ”sketch” of the UI in Flash. Without even thinking, I started up Visual Studio and began laying out a few controls to port the sketch. Hmm…should I skin the controls or derive new ones? Should I draw everything manually with the Graphics object? Should I load some vector art from a metafile? How was I going to perform those animations and transitions? Ugh…it was going to take a long time to implement the UI before I could even get around to real processing functionality. Then it hit me, why not use Flash for the UI? The client had most of the work already done, he simply needed a Windows application to do some behind the scenes processing that would be hard given the constraints of Flash sandboxing.

Today’s post covers how to host Flash in a C# application with two-way communcation between environments. Here’s a screenshot of the demo:

Clicking the green button in Flash pops up a message box in C#. Entering text in C# and clicking the Send button echos it in Flash.
The first step is to add the Shockwave Flash ActiveX control to the Toolbox (it doesn’t matter which group you add the control to):

After selecting the component from the COM tab, you should see a new icon in your Toolbox:

Now, drag ‘n drop this control onto Form1 and you should get the following:

Something broke with the release of VS2005. You should have no problem doing the above in VS2003. However, I don’t have VS2003 so what’s the solution? As it turns out, the answer is in the warning messages. There was a failure to create a wrapper assembly due to a COM exception. Well, I know of another .NET language that should have no problem interacting with COM…

Now create a new FlashPanel user control:

The FlashPanel is a derivative of UserControl. In the designer, repeat the above steps – drag ‘n drop the Flash object onto the panel and set its Dock property to ”Fill”. The C++/CLI control library will successfully create a wrapper assembly for the ActiveX control. Add the following property so C# can access the Flash object:


public: property AxShockwaveFlashObjects::AxShockwaveFlash^ Flash
{
    AxShockwaveFlashObjects::AxShockwaveFlash^ get()
    {
        return this->axShockwaveFlash1;
    }
}
 

Once you build the control library, you should see your new UserControl in the Toolbox:

Now create a C# Windows application. Under the References folder, add the following:

Next, we begin using our FlashPanel. The ActiveX Flash control has a LoadMovie function which takes an absolute directory path to the .swf file. It also has an FsCommand delegate:


public Form1()
{
    InitializeComponent();
    string moviePath = Application.StartupPath + "test.swf";
    this.flashPanel1.Flash.LoadMovie(0, moviePath);
    this.flashPanel1.Flash.FSCommand += new
        AxShockwaveFlashObjects._IShockwaveFlashEvents_FSCommandEventHandler(
                                                            OnFsCommand);
}
 

FsCommand is used by Flash to communicate with the host environment. In the test.swf file, the green arrow button has the following ActionScript attached to it:


on(release)
{
    fscommand("ButtonClick", "You clicked a button in Flash");
}
 

OnFsCommand will respond to messages from Flash. In this example, it only checks for the “ButtonClick” message:


void OnFsCommand(object sender,
                 AxShockwaveFlashObjects._IShockwaveFlashEvents_FSCommandEvent e)
{
    if( e.command == "ButtonClick")
        MessageBox.Show(e.args);
}
 

When sending a message to Flash, we use SetVariable passing both the message name and value:


private void OnSendButtonClick(object sender, EventArgs e)
{
    this.flashPanel1.Flash.SetVariable("DotNetMessage", this.textBox1.Text);
}
 

If you wanted to send more information in the value param you could use a delimiter. For example, “foo?bar” would need to be parsed and split based on ”?” characters to extract multiple values.
The last bit of ActionScript code is used to setup event listening. The root watches for a “DotNetMessage” and then passes it along to the listener object. The code in this example, simple echos any value to the textbox control inside Flash.


var listenerObj:Object = new Object();
listenerObj.onMessage = function (prop, oldVal, newVal)
{
    _root.textBox = newVal;
}
_root.watch("DotNetMessage", listenerObj.onMessage);
 

The .fla file is available in the Debug directory. You can download the source code here.

Written by admin

June 2nd, 2006 at 9:46 pm

Posted in code

custom serialization

without comments

For a derivative class to be serializable, the base class must be marked as serializable as well. For example, if I do the following:


[Serializable]
public partial class Form1 : Form
 

And attempt to serialize the object, I will get an exception.

Adding a serializable attribute to the base Form is not possible and many people at this point move on looking for another solution. However, it is possible to make Form1 serializable through custom serialization. To do this, first add ISerializable to the inheritance chain:


[Serializable]
public partial class Form1 : Form, ISerializable
 

And then implement it:


void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
{
    info.AddValue("Location", this.Location);
    info.AddValue("ClientSize", this.ClientSize);
}
 

An exception is no longer produced when running the code now. After execution, you should find a ”Form1.bin” file in the Debug directory. Despite this, a binary file is not very user-friendly. Let’s modify the code so we can actually open the file and read it. Rather than using a BinaryFormatter, why not a SoapFormatter? To use soap formatting you’ll need to add a reference to the assembly:


using System.Runtime.Serialization.Formatters.Soap;
//...

FileStream fileStream = File.Create("Form1.xml");
IFormatter iFormatter = new SoapFormatter();
iFormatter.Serialize(fileStream, form);
 

In the above code, I’ve changed the file name from Form1.bin to Form1.xml. However, the file extension doesn’t impact the content. Here’s a sample of the output:

So, serialization works but what about deserialization? To accomplish that, you need to implement a deserialization constructor:


private Form1(SerializationInfo info, StreamingContext context)
{
    DefaultInitialization();
    this.Location = (Point)info.GetValue("Location", this.Location.GetType());
    this.ClientSize = (Size)info.GetValue("ClientSize", this.ClientSize.GetType());
}
 

The DefaultInitialization function calls InitializeComponent and then assigns some default values. Calling InitializeComponent is important if you want the Form and any of its controls to be initialized appropriately.


private void DefaultInitialization()
{
    InitializeComponent();
    this.StartPosition = FormStartPosition.Manual;
    this.Location = new Point(0, 0);
    this.ClientSize = new Size(640, 480);
}
 

The DefaultInitialization function is also called from the default constructor:


public Form1()
{
    DefaultInitialization();
}
 

Deserialization can’t occur after Application.Run returns because the Form has already been destroyed. Here, we plug-in to the FormClosing event to handle deserialization:


private void OnFormClosing(object sender, FormClosingEventArgs e)
{
    FileStream fileStream = new FileStream("Form1.xml",
                                           FileMode.Create,
                                           FileAccess.Write);

    IFormatter iFormatter = new SoapFormatter();
    iFormatter.Serialize(fileStream, this);

    fileStream.Close();
}
 

Finally, we can bring it all together in Main:


[STAThread]
static void Main()
{
    Form1 form = null;

    if (File.Exists("Form1.xml"))
    {
        FileStream fileStream = File.Open("Form1.xml", FileMode.Open);
        IFormatter iFormatter = new SoapFormatter();
        form = iFormatter.Deserialize(fileStream) as Form1;

        fileStream.Close();
    }

    if (form == null)
        form = new Form1();

    Application.Run(form);
}
 

With this simple example, you can position and resize the Form; restart the application and it will remember your previous settings. Now I’m not saying this is a way to save Form settings. There are numerous methods including:

  • app.config
  • .resources
  • .ini
  • manual parsing of various file formats

The real demonstration here is how to make an object serializable when the base class isn’t.

Written by admin

June 2nd, 2006 at 12:52 am

Posted in code

misc directx

without comments

ProgressiveMeshDemo.zip – A simple demo of the D3D progressive mesh interface using C++/CLI.

PickingDemo.zip – A simple ray picking demo using DirectX and C++/CLI .Drag and click the arrow to slide it back and forth.

OrthoPickingDemo.zip – A simple demo of picking in an orthographic viewport using DirectX and C++/CLI:

LerpDemo.zip – C++ DirectX demo of linear interpolation between keyframes using an assembly shader.

Written by admin

May 22nd, 2006 at 10:14 pm

Posted in code

wireframe

without comments

What’s the “minimum” amount of C# code you need to create a spinning wireframe quad in 3D?


using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Diagnostics;

namespace Wireframe1
{
    public partial class Form1 : Form
    {
        List<Vertex> vertices = new List<Vertex>();

        Point prev  = new Point();
        Point curr  = new Point();
        Point first = new Point();

        float angle = 0.0f;

        public Form1()
        {
            InitializeComponent();

            vertices.Add(new Vertex(-1.0f, -1.0f, 0.0f));
            vertices.Add(new Vertex(-1.0f,  1.0f, 0.0f));
            vertices.Add(new Vertex( 1.0f,  1.0f, 0.0f));
            vertices.Add(new Vertex( 1.0f, -1.0f, 0.0f));
        }

        private void OnTick(object sender, EventArgs e)
        {
            this.Invalidate();
        }

        private void OnPaint(object sender, PaintEventArgs e)
        {
            // rotate quad a little
            int rotationTime = 8000;
            int time = System.Environment.TickCount % rotationTime;
            angle = time * (2.0f * (float)Math.PI) / rotationTime;

            for (int i = 0; i < 4; ++i)
            {
                // we don't want to alter the original vert
                Vertex v = new Vertex(vertices[i].x,
                                      vertices[i].y,
                                      vertices[i].z);

                float oldX = vertices[i].x;
                float oldY = vertices[i].y;
                float oldZ = vertices[i].z;

                // y-axis rotation
                v.x = oldX * (float) Math.Cos(angle) +
                      oldZ * (float)Math.Sin(angle);
                v.z = oldX * (float)-Math.Sin(angle) +
                      oldZ * (float)Math.Cos(angle);

                // view transform - translate inverse cam
                v.z -= -2.4f; // move camera back slightly

                // - simple perspective divide (not the best looking)
                // - note: building a perspective matrix with an
                //         arbitrary field of view is more involved
                v.x /= v.z;
                v.y /= v.z;

                // screen transform
                v.x =  v.x * 640 / 2 + 640 / 2;
                v.y = -v.y * 480 / 2 + 480 / 2;

                curr.X = (int)v.x;
                curr.Y = (int)v.y;

                if (i == 0)
                {
                    first = curr;
                    prev = curr;
                    continue;
                }

                e.Graphics.DrawLine(Pens.Black, prev, curr);
                prev = curr;

            } // end for

            e.Graphics.DrawLine(Pens.Black, curr, first);

        } // end OnPaint

    } // end Form

    public struct Vertex
    {
        public float x;
        public float y;
        public float z;

        public Vertex(float x, float y, float z)
        {
            this.x = x;
            this.y = y;
            this.z = z;
        }

    } // end Vertex

} // end namespace
 

Written by admin

May 21st, 2006 at 2:08 am

Posted in code

bias

without comments

Programming is a basic skill, like reading and writing. It’s something you should possess if you’re going to successfully navigate the information age. I can’t imagine living in a world of computers and not knowing how to program – it’s absurd. Programming is sheer power and with it you can implement your ideas.

To some, programming is perceived as purely technical, there’s no art to writing a program. Of course, nothing could be further from the truth. Programmers create software by designing (and implementing) architectures of interweaving patterns of abstract concepts. However, before you get to that level of skill, you first have to master the technical. And like any other discipline, be it painter or craftsman, mastery of the technical can take years before you are able to express yourself.

Written by admin

May 20th, 2006 at 8:43 pm

Posted in blather

signing C++/CLI assemblies

with one comment

Stubbornness is a form of persistence and I’m continually amazed at my inability to give something up and move on – at least when it comes to programming. Getting a signed C++/CLI DLL to work with a C# project had me stumped for a while. Here’s the process I went through.

First, create a new solution:

Now add, a C++/CLI class library project:

I’ve called the project ClassLibrary1 and added some trivial code:


namespace ClassLibrary1
{
    public ref class Class1
    {
        public: void Foo()
        {
            Console::WriteLine("Bar");
        }
   };
}
 

The first step to making this a strong-named DLL is to generate a cryptographic key pair using sn.exe. This command line utility is located under the “Microsoft Visual Studio 8SDKv2.0Bin” directory path. To execute sn.exe you’ll need a Run prompt. To bring up the Run prompt hit the Windows+R key combo. Now type CMD and hit Enter:

You have to navigate to the Bin directory mentioned earlier. The DOS command to do this is called CD (change directory). For example:

Call sn.exe with the -k option to generate a new key pair to the specified file. In this example, a new key pair is generated and written to the file ClassLibrary1.snk:

Grab the newly generated file from the Bin folder and cut ‘n paste it into your project folder:

Inside of AssemblyInfo.cpp, add the following line of code:


[assembly:AssemblyKeyFile("ClassLibrary1.snk")];
 

Compile the project and mt.exe (manifest tool) returns the following:


general warning 810100b3: ..debugClassLibrary1.dll is a strong-name
signed assembly and embedding a manifest invalidates the signature.
You will need to re-sign this file to make it a valid assembly.
 

Okay…So let’s get rid of the embedded manifest:

A rebuild proves successful. Now add a CLR console app:

Right-click the project and select References to bring up the following dialog:

Under the Projects tab select ClassLibrary1 and click OK:

You should see the newly added ClassLibrary1 in the References section. None of the Build Properties need to be altered.

I’ve added the following code to test things out:


using namespace System;
using namespace System::Reflection;

int main(array<System::String ^>^ args)
{
    ClassLibrary1::Class1 c1;
    c1.Foo();

    for each(Assembly^ a in AppDomain::CurrentDomain->GetAssemblies())
    {
        Console::WriteLine(a->FullName);
    }

   Console::ReadLine();
   return 0;
}
 

Set ConsoleApplication1 as the startup project and test it app. Output example:

The output shows the full name of ClassLibrary1 with PublicKeyToken and hash value. Now, you may think everything is working fine but let’s see what happens when a C# project tries to use ClassLibrary1.dll. Add a new C# console project and a reference to ClassLibrary1:

As soon as you do so, the following warning pops up:

And sure enough, if you compile it, the build fails with a ResolveNativeReference task failure. So, delete the reference to ClassLibrary1. Add the reference again but this time, do so from the Browse tab:

Recompile and it builds successfully. Now, just because it compiles doesn’t mean it works, so let’s test it out.


using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;

namespace ConsoleApplication2
{
    class Program
    {
        static void Main(string[] args)
        {
            ClassLibrary1.Class1 c1 = new ClassLibrary1.Class1();
            c1.Foo();

            foreach (Assembly a in AppDomain.CurrentDomain.GetAssemblies())
            {
                Console.WriteLine(a.FullName);
            }

            Console.ReadLine();
        }
    }
}
 

Hit F5 to launch a debug session and:

The specified module could not be found. Great…but which module? After messing with this failure for a long time it turns out I should have signed the assembly in a different manner (not using the AssemblyKeyFile attribute). Have you ever signed a C# DLL? If you open the AssemblyInfo.cs file (it’s inside the Properties folder) and add the following:


[assembly: AssemblyKeyFile("SomeCSharpLibrary.snk")]
 

It returns with a warning not to use it.


Use command line option '/keyfile' or appropriate project settings instead of
'AssemblyKeyFile'
 

Visual Studio prefers you go under project properties to set the stong name.

Okay. Perhaps we can do something similar with the C++/CLI DLL? In the ClassLibrary1 project, remove the AssemblyKeyFile attribute from the AssemblyInfo.cpp file. Under the project properties, restore Embed Manifest to Yes. Now go to the Advanced section and set:

A rebuild proves successful. Run it and it works! Finally…

In the future, don’t forget to add the library via the Browse tab or you’ll get a ResolveNativeReference error.

Written by admin

May 20th, 2006 at 6:23 pm

Posted in code