HOW TO: How can I call methods in .NET assemblies?

Reference: Q0040

Article last modified on 29-Dec-2006


The information in this article applies to:

  • XLL+ for Visual Studio .NET - 4.2, 4.3.1, 5.0

HOW TO: How can I call methods in .NET assemblies?

Question

I want to use methods from the .NET runtime libraries (or other code in .NET assemblies authored in C#, VB.NET or managed C++). What do I have to do to get it to work safely using Visual Studio .NET (2003)?

Answer

There are 3 issues to address:

  1. Change the compiler settings for the project.
  2. Use Managed C++ to call the .NET code.
  3. Use a bootstrap XLL to load your XLL, and override the virtual method CXllApp::GetRegDllName().

The steps described below use an example XLL named ClrUser.xll. This was created using the XLL+ AppWizard (and selecting the STL run-time library). Wherever you see ClrUser you should replace it with the name of your XLL.

The sample project can be downloaded using the link below.

Compiler settings

  1. Use the Project/Properties menu to open the Project Properties dialog. Click the Configuration Properties folder. Click the General property page. Modify the Use Managed Extensions property to Yes.

  2. Click the Debugging folder. Modify the Command Arguments property to "$(TargetDir)/XnLoader.xll" (including the quotes).

  3. Click the Linker folder. Click the Input page. Add the following libraries to the Additional Dependencies property:

    xlllibsr.lib msvcrt.lib libcmt.lib

    In the Debug build, use these libraries instead:

    xlllibsd.lib msvcrtd.lib libcmtd.lib
  4. Still on the Input page, add the following symbol to the Force Symbol References property:

    __DllMainCRTStartup@12
  5. Remaining within the Linker folder, click the Advanced page. Modify the Resource Only DLL property to Yes (/NOENTRY).

  6. Click the Build Events folder. Click the Post-Build Event page. Modify the Command Line property to look as follows:

    copy "$(ProjectDir)..\XnLoader\$(OutDir)\XnLoader.xll" "$(TargetDir)"
    copy "$(ProjectDir)XnLoader.cfg" "$(TargetDir)"

    This assumes that the XnLoader project is in a directory that is a sibling to the project directory. In the case of the samples, this is true - the sample project is in Samples/ClrUser, and the XnLoader project is in Samples/xnLoader. If your XnLoader project is located elsewhere, you should amend the lines above.

Use Managed C++ from your add-in

This is not the place to go into further details of using Managed C++. These notes describe the minimum changes required to let your project call the CLR.

  1. In ClrUser.h add the following line, after #include <xllplus.h>:
    #using <mscorlib.dll>
  2. In ClrUser.def, add the following lines:

    	crtInit   PRIVATE
    	crtTerm   PRIVATE
  3. Copy the file init.cpp from the ClrUser sample folder into your project folder, and add it to the project.

  4. Add the following line to ClrUser.h:

    	virtual void GetRegDllName(CXlOper& xloDllName);
  5. Add the following declaration and method to ClrUser.cpp:

    extern const char* GetFullPathForLoadedDLL();   // init.cpp
    
    void CClrUserApp::GetRegDllName(CXlOper& xloDllName)
    {
        xloDllName = GetFullPathForLoadedDLL();
    }
  6. Copy the file xnloader.cfg from the ClrUser sample folder into your project folder. Edit the file so that it contains the unqualified name of your real XLL in the first line, e.g.:

    ClrUser.xll
  7. Add an add-in function that makes use of the CLR. Use Managed C++ to call CLR methods. e.g.:

    extern "C" __declspec( dllexport )
    LPXLOPER ClrNow()
    {
        XLL_FIX_STATE;
        CXlOper xloResult;
    //}}XLP_SRC
    
        try
        {
            System::String* str = System::String::Format("{0}", __box(System::DateTime::Now));
            xloResult = NetToXL(str);
        }
        catch(System::Exception __gc* ex)
        {
            xloResult.Format("#ERROR: %s", NetToXL(ex->Message).c_str());
        }
        return xloResult.Ret();
    }
    

Build and run

  1. Build the project.

    If you get an error The system cannot find the file specified. during the Post-Build Event, check the locations of XnLoader.xll and XnLoader.cfg (see step 6 above.)

  2. To run the project, always open XnLoader.xll (located in the same directory as the real XLL), instead of the real XLL.

Your project is now ready to use .NET assemblies. If you using your own assemblies, then be sure to make them accessible to EXCEL.EXE at run-time. You can do this in several ways, including:

  • Adding your assemblies to the GAC.
  • Locating your assemblies in the folder which contains EXCEL.EXE.
  • Adding a codepath to excel.exe.config.

Sample project

The sample project disucssed in the steps above can be downloaded here.

The solution file ClrUser\ClrUser.sln contains the sample project and also the bootstrapper project, XnLoader.vcproj.

If you unzip the file into your XllPlus samples directory (typically C:\Program Files\Planatech\XllPlus\Samples) it will build immediately, without any need to change the Include and Lib paths.

Why a bootstrap XLL?

A serious problem can arise when initializing the C run-time libraries in a library which uses the .NET Common Language Runtime. Because of indeterminacy in the order of initialization, the process can lock indefinitely. For more details about the problem, see Mixed DLL Loading Problem and You receive linker warnings when you build Managed Extensions for C++ DLL projects on the Microsoft web-site.

The solution to this problem is to use a bootstrap XLL to do the following:

  • Respond to Excel's required callback functions (xlAutoOpen() etc).
  • Explicitly load the real XLL into memory using LoadLibrary().
  • Explicitly initialize the C run-time library on behalf of the real XLL.

A sample bootstrap XLL is provided, which can be used to load your real XLL.

Loading your XLL

To make the bootstrapper load your specific XLL, you should do the following:

  1. Edit the file XnLoader.cfg to contain the name of your XLL.
  2. Ensure that XnLoader.xll and XnLoader.cfg are located in the same directory as your real XLL.
  3. Open (or select in Tools/Add-ins) XnLoader.xll instead of your real XLL.

How the Bootstrapper works

The two most important constraints that apply to the design of the bootstrapper are:

  1. The C runtime library for the real XLL must be initialized before any CLR methods are called.
  2. LoadLibrary() may not be called during DllMain().

Therefore, the bootstrapper does not load the real XLL during DllMain(), but delays until the first Excel API callback is made. (This will usually be xlAutoOpen(), but may be xlAddInManagerInfo().)

For each callback function, the bootstrapper does the following:

  1. Checks whether the real XLL has been loaded.
  2. If it has not been loaded, loads the XLL, and calls the CRT initialization function (crtInit).
  3. Redirects the call to the identically named function (e.g. xlAutoOpen(), xlAutoFree() etc) in the real XLL.

See also

q0040_clruser.zip, the sample discussed above.
Mixed DLL Loading Problem on the Microsoft website.
You receive linker warnings when you build Managed Extensions for C++ DLL projects on the Microsoft web-site.