Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

Wrapping C with C++ in .NET


A common software requirement is for code written in one computer language to be used by software developed in a different computer language. For instance, it may be necessary to access C functions from a .NET or Java application. In this article, I show how existing C software can be wrapped in C++ and thus made easily accessible from .NET languages such as C# and VB.NET. Although I use the NAG C library from the Numerical Algorithms Group (http://www.nag.co.uk/) as an example, the method is general and can be applied to other C software. In particular, I illustrate the technique by considering four NAG C library numeric routines, which have applications in computational finance:

  • NAG function s15abc, the cumulative normal distribution that is used in analytic option pricing formulae, such as the Black-Scholes equation [1].

  • NAG function f02aac, the eigenvalue computation. This has applications in multifactor models, including interest-rate models and time series [2], [3].

  • NAG function d01ajc, numerical integration, which has applications in risk analysis [4].

  • NAG function e04dgc, numerical optimization. This can be used to compute optimal portfolios [5].

COM, .NET Assemblies, and Managed C++

Microsoft COM already enables the creation of numeric components that can be used by the complete range of Windows programming languages. In fact, COM objects can be used within .NET. However, from a programmer's point of view, wrapping C code in COM C++ classes has the disadvantage that there is visible Microsoft COM baggage that needs to be carried around. This has the effect of obscuring the code and also making it difficult to implement the C++ classes on UNIX platforms. Another limitation is that the classes contained within a COM object cannot be used to create other derived classes.

.NET assemblies have improved this situation. In a nutshell, the classes in an assembly can be coded in any .NET language, then used by any other .NET language. Thus, it is possible to create assemblies in managed C++ that provide class wrappers for C routines, then use these from C# and VB.NET software [6].

Listing 1 shows the ANSI function prototypes of four NAG C functions. Listing 2 is the managed C++ code used to create an assembly that wraps the C functions. I call this assembly "naglib," and it defines the namespace NAGLIB and the managed class NAG_FUNCTIONS, which provides functions to access native C routines contained within the DLL "nagc." Listing 2 is meant for illustrative purposes and not intended to be a statement of good programming practice. However, I have included some useful features, such as flagging errors and setting default parameter values via the constructor NAG_FUNCTIONS().

As you can see, the code is almost standard C++ and (in contrast to the equivalent COM approach) could easily be ported to UNIX platforms. I now examine each nonstandard C++ (that is, Microsoft-specific) feature.

The NAG C library routines used are contained in a DLL called "nagc." Here, each function is imported into the C++ project by name. For instance:

[DllImport(</b>"<b>nagc</b>"<b>)]
extern </b>"<b>C</b>"<b> Double s15abc(Double x);

is used to import the function s15abc, which computes the cumulative normal distribution.

The directive gc indicates that the code is managed and memory is allocated on the garbage collected (GC) heap; unmanaged code is indicated by nogc.

In Listing 2, the .NET data types Double and Int32 have been used so that the assembly can be accessed by both C# and VB.NET code. All managed .NET code written in VB.NET, C#, and C++ is compiled to the same intermediate language (IL) code. To permit interoperability within .NET, there is a common type system (CTS) that standardizes the basic data types across all languages. Table 1 provides a summary of the .NET data types corresponding to the C++ types double and long.

In the case of numerical integration and optimization, user-defined functions (or call-back functions) need to be passed as parameters to the NAG C library routine. This is done in .NET by declaring a delegate with the same signature (that is, return type and parameter types) as the callback function. For example:

public __delegate double INTEGRAND_FUN_TYPE(Double x);

declares the delegate INTEGRAND_FUN_TYPE with a signature corresponding to functions that return a Double and have a single Double parameter passed by value. This delegate is used by the numerical integration routine d01ajc for defining the integrand. The declaration of a delegate is similar to the declaration of a function prototype with the additional words public (or private) and delegate. Also, the declaration and use of delegates in Listing 2 has similarities with the declaration and use of function pointers in Listing 1.

A more complicated delegate example is:

public __delegate void OBJ_FUN_TYPE (Int32 n, double *x, double * objf, double *g, Int32 comm);

which declares the delegate OBJ_FUN_TYPE. This is done with a signature that applies to subroutines (that is, functions that return void) with parameters of type Int32 and double *. As you can see, I had to use double * instead of the more general Double *. This delegate is used by the numerical optimization routine e04dgc for specifying the objective function to be minimized.

Accessing the Naglib Assembly from C#

The C# code in Listing 3 (and project screen in Figure 1) illustrate how the assembly naglib can be accessed from a C# console project created using Visual Studio .NET. You can see that the C# code defines the classes DCLASS and RUNIT. DCLASS is derived from the class NAG_FUNCTIONS and supplies definitions for the callback functions used by the member functions OPTIMIZE and QUADRATURE.

The class RUNIT only contains the member function Main. This function is run by the example console application, and all the computations are performed by a single numeric object (tt) of type DCLASS.

The assembly containing the namespace NAGLIB is accessed with the statement using NAGLIB, which occurs on the third line of Listing 3. (See Listing 4 for the output from Listing 3.) I use Math.PI to return the value of , Math.Sin(x) to compute sin(x), and Math.Exp(x) to evaluate exp(x). These functions are members of the class Math, which is contained in the namespace System.

This directive is necessary because C# does not really support pointers. The keyword unsafe lets you use pointers and thus easily pass scalars and arrays by reference to the managed C++ class NAG_FUNCTIONS contained in the namespace NAGLIB.

The statement DCLASS tt = new DCLASS() creates a numeric object tt with the type of the derived class DCLASS. Since DCLASS was derived from NAG_FUNCTIONS, it allows access not only to the public member functions objfun and the_integrand_c, but also the public member functions of NAG_FUNCTIONS: CUM_NORM, OPTIMIZE, QUADRATURE, and REAL_SYMM_EIGEN. This means that I can compute the cumulative normal distribution and perform eigenvalue computations by using statements of the form:

the_answer = tt.CUM_NORM(x);
flag = 0;
// first row
a[0,0] = 0.5;
a[0,1] = 0.0;
 ...
// fourth row
a[3,0] = -2.6;
a[3,1] = -0.7;
a[3,2] = 0.0;
a[3,3] = 0.5;
tt.REAL_SYMM_EIGEN(n, ref a[0,0], tda, ref r[0], ref flag);

Note that the keyword ref is used to pass the address of a[0,0], r[0], and flag to the member function REAL_SYMM_EIGEN.

Using Numeric Objects with Member Functions Requiring Delegates

We will now consider how to call the numerical integration function QUADRATURE. This is achieved using the following C# statement:

INTEGRAND_FUN_TYPE myfun_c = new INTEGRAND_FUN_TYPE (tt.the_integrand_c);

to declare (and also define) the delegate myfun_c, of type INTEGRAND_FUN_TYPE, which corresponds to the user-defined function the_integrand_c contained in the derived class DCLASS. The next step is to pass the appropriate parameters to the function tt.QUADRATURE. For example:

a1 = 0.0;
b1 = Math.PI*2.0;
flag = 0;
the_answer = 0.0;
tt.QUADRATURE(a1, b1, ref the_answer, ref abserr, ref flag, myfun_c);

The method for calling the numerical optimization member function is similar. For instance, in the example code, we use:

OBJ_FUN_TYPE myobjfun = new OBJ_FUN_TYPE (tt.objfun);
x2[0] = -1.0;
x2[1] = 1.0;
n2 = 2;
flag = 0;
tt.OPTIMIZE(n2, ref x2[0], ref g[0], ref objf, ref flag, myobjfun);

The initial parameter estimates and computed optimal values are contained in the array x2. The estimated gradient at the solution point is returned in the array g, and the parameter objf contains the value of the minimized objective function.

Accessing the Naglib Assembly from VB.NET

The assembly naglib can be used from VB.NET in a similar manner to that described for C#; see Listing 5.

Conclusion

A major benefit of the approach presented here over COM is that the managed C++ wrapper code can, with little effort, be used on UNIX platforms. In addition, unlike COM, you can create C# or VB.NET derived classes from the managed C++ (base) classes. As more software supports .NET (as, for example, Excel 2003 will), the future of object-oriented numerics in .NET looks promising.

References

[1] Black, F. and M. Scholes. "The Pricing of Corporate Liabilities," Journal of Political Economy, 1973.

[2] Rebonato, R. Interest-rate Option Models, Second Edition, John Wiley, 1998.

[3] Levy, G.F. Computational Finance, Numerical Methods for Pricing Financial Instruments, Heinemann Press, 2003.

[4] Hull, J.C. Options Futures and Other Derivatives, Prentice Hall, 1997.

[5] Markowitz, H.M. "The General Mean-Variance Portfolio Selection Problem," Phil. Trans. R. Soc. Lond. 1994.

[6] Challa, S. and A. Laksberg. Essential Guide to Managed Extensions for C++, Apress, 2002.


George Levy, who holds a doctorate in mathematical physics from Oxford University, is a consulting software engineer and author of the upcoming book Computational Finance: Numerical Methods for Pricing Financial Instruments. He can be contacted at [email protected].



Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.