CLR provides platform invoke mechanism to enable managed applications to call unmanaged APIs exported in a DLL. Here is a simple example:

using System.Runtime.InteropServices;

public class HelloWorld {
    public static void Main() {
       MessageBox(0, "Hello World", "Platform Invoke Sample", 0);
    }

     [DllImport("user32.dll", CharSet=CharSet.Auto)]
     public static extern int MessageBox(int hWnd, String text, String caption, uint type);

CLR will search the dll in your assembly's directory first, then search the dll in directories listed in PATH environment variable.

If the dll is not in any of those directories, you have to use so called Dynamic PInvoke technique.

There are two known good dynamic PInvoke techniques in .Net framework 1.0 and 1.1:

1. Call LoadLibrary before you call the exported API. Example: http://www.dotnetinterop.com/faq/?q=LoadLibrary

2. Use Reflection.Emit. Example: http://www.msjogren.net/dotnet/eng/samples/dotnet_dynpinvoke.asp

Of course, the newest and greatest Whidbey (.Net framework 2.0) will save the world. It introduces a new mechanism for dynamic PInvoke --- Marshal.GetDelegateForFunctionPointer.

Here is an example:

using System.Runtime.InteropServices;
using System;

public class TestClass
{
    public static void Main(String[] args)
    {
        IntPtr user32 = LoadLibrary("user32.dll");
        IntPtr procaddr = GetProcAddress(user32, "MessageBoxW");
        MyMessageBox mbx = (MyMessageBox)Marshal.GetDelegateForFunctionPointer(procaddr, typeof(MyMessageBox));
        mbx(IntPtr.Zero, "Hello, World", "A Test Run", 0);
    }

    internal delegate int MyMessageBox(IntPtr hwnd, [MarshalAs(UnmanagedType.LPWStr)]String text, [MarshalAs(UnmanagedType.LPWStr)]String Caption, int type);

    [DllImport("kernel32.dll")]
    internal static extern IntPtr LoadLibrary(String dllname);

    [DllImport("kernel32.dll")]
    internal static extern IntPtr GetProcAddress(IntPtr hModule, String procname);
}

This example is tested in whidbey beta1.