How do I share a constant between C# and C++ code?
I'm writing two processes using C# and WCF for one and C++开发者_如何学JAVA and WWSAPI for the second. I want to be able to define the address being used for communication between the two in a single place and have both C# and C++ use it. Is this possible?
The closest I've come is defining the constant in an IDL, then using MIDL and TLBIMP to get it into a DLL that can be consumed by C#. However this doesn't seem to expose the constant, or at least I can't figure out how to make it do so. Maybe it is limited to type definitions only.
Any other suggestions?
You can create a separate C++/CLI project and define all your constants in a .h
file. For example, create C++/CLI Class Library project called "ConstantBridge" and a C# project called "CSharpProgram":
Constants.h
namespace Constants
{
const int MAGIC_NUMBER = 42;
}
// String literals must be defined as macros
#define MAGIC_PHRASE "Hello World"
// Since stirngs must be macros it's arguably more consistent
// to use `define` throughout. This is for demonstration purpose.
ConstantBridge.h
#include "Constants.h"
namespace ConstantBridge { public ref class ConstantBridge {
public:
// The use of the `literal` keyword is important
// `static const` will not work
literal int kMagicNumber = Constants::MAGIC_NUMBER;
literal String ^ kMagicPhrase = MAGIC_PHRASE;
};}
CSharpProgram.cs
Console.WriteLine(ConstantBridge.kMagicNumber); // "42"
Console.WriteLine(ConstantBridge.kMagicPhrase); // "Hello World"
Now, have the "CSharpProgram" project reference the "ConstantBridge" project. Your other native C++ projects can simply #include "Constants.h"
.
As long as you reference only literal
s from the ConstantBridge
project, a runtime dependency will not be generated. You can verify using ILSpy or ILdasm. const
in C# and literal
in C++/CLI are copied "literally" to the call site during compilation.
C# and C++ have differing models for constants. Typically, the constant won't even be emitted in the resulting C++ binary -- it's automatically replaced where it is needed most of the time.
Rather than using the constant, make a function which returns the constant, which you can P/Invoke from C#.
Thus,
#include <iostream>
const double ACCELERATION_DUE_TO_GRAVITY = 9.8;
int main()
{
std::cout << "Acceleration due to gravity is: " <<
ACCELERATION_DUE_TO_GRAVITY;
}
becomes
#include <iostream>
extern "C" double AccelerationDueToGravity()
{
return 9.8;
}
int main()
{
std::cout << "Acceleration due to gravity is: " <<
AccelerationDueToGravity();
}
which you should be able to P/Invoke from C#.
Wasn't happy with the other solutions for my use case so coded up a slightly hacky solution that seems to fit the original request better; a constant in one file that can be built into both a C# and a C++ project...
- Version information in a .cs file, in a common location.
Like this:
// Version.cs
public static class MyAppVersion
{
//build
public static string Number = "1.0";
public static string Phase = "Alpha";
//configuration (these are the build constants I use, substitute your own)
#if BUILD_SHIPPING
public static string Configuration = "Shipping";
#elif BUILD_DEVELOPMENT
public static string Configuration = "Development";
#elif BUILD_DEBUG
public static string Configuration = "Debug";
#else
"build type not defined"
#endif
}
- Include in C# project using Add Existing Item... [Add As Link]
- Include in C++ project (in a .cpp file) with a
#include
Like this:
//include version information into a .cpp
#define class namespace
#define public
#define static
#define string const char*
#include "..\..\Version.cs" //or to where-ever your file is
;
#undef class
#undef public
#undef static
#undef string
- Reference in C# with:
MyAppVersion.Number
- Reference in C++ with:
MyAppVersion::Number
When I've had to do that stuff in the past, I've simply added an extra pre-compilation step to the build process which automagically creates one file from another.
Since your constants will probably be within a class in C#, you can use that as the source file:
MyClass.cs:
class MyClass {
public const int NUM_MONTHS = 12; //COMMON
public const int YEAR_BASE = 1900; //COMMON
}
grep '//COMMON' MyClass.cs
| sed -e 's/^ *public const [a-z][a-z]*/#define/'
-e 's/ *= */ /'
-e 's/;.*$//'
>MyClass.h
grep '//COMMON' MyClass.cs | sed -e 's/ *public //' -e 's/;.*$/;/' >MyClass.hpp
This will give you:
MyClass.h:
#define NUM_MONTHS 12
#define YEAR_BASE 1900
MyClass.hpp:
const int NUM_MONTHS = 12;
const int YEAR_BASE = 1900;
Now, getting Visual Studio to perform that step is not something I know how to do. You'll have to investigate whether or not it's even possible. The UNIXy text processing tools are really worth downloading. I have CygWin installed on a few boxes but, for something this localised, you could get away with individual GnuWin32 packages.
You could probably do a similar job in PowerShell but I'm not really well versed in that.
Now that's a bit of a kludge so may I suggest a possibly better way for you particular question. Don't use a constant at all. Put the address into a configuration file and have your C# and C++ code read it at startup.
That way, you get to share the value painlessly and it's configurable in case you ever want to change it in future.
An easier alternative to a method for each constant may be a class containing the constants as instance properties. You could create it in C# and expose it via COM interfaces to C++. That's easier and less error-prone than P/Invoke, since you don't have to worry about getting all the types and names right - it's all done for you by the compiler.
Note: I have not tried this, I'm only speculating that it should work.
精彩评论