This topic explains how native functions that accept C-style strings can be called using the CLR string type System::String using .NET Framework Platform Invoke support. C++ programmers are encouraged to use the C++ Interop features instead (when possible) because P/Invoke provides little compile-time error reporting, is not type-safe, and can be tedious to implement. If the unmanaged API is packaged as a DLL, and the source code is not available, then P/Invoke is the only option, but otherwise see Using C++ Interop (Implicit PInvoke).
Managed and unmanaged strings are laid out differently in memory, so passing strings from managed to unmanaged functions requires the
As with functions that use only intrinsic data types,
Example
The following code consists of a unmanaged and a managed module. The unmanaged module is a DLL that defines a function called TakesAString that accepts a C-style ANSI string in the form of a char*. The managed module is a command-line application that imports the TakesAString function, but defines it as taking a managed System.String instead of a char*. The MarshalAsAttribute attribute is used to indicate how the managed string should be marshaled when TakesAString is called.
The managed module is compiled with /clr, but /clr:pure works as well.
В | Copy Code |
---|---|
// TraditionalDll2.cpp // compile with: /LD /EHsc #include <windows.h> #include <stdio.h> #include <iostream> using namespace std; #define TRADITIONALDLL_EXPORTS #ifdef TRADITIONALDLL_EXPORTS #define TRADITIONALDLL_API __declspec(dllexport) #else #define TRADITIONALDLL_API __declspec(dllimport) #endif extern "C" { TRADITIONALDLL_API void TakesAString(char*); } void TakesAString(char* p) { printf_s("[unmanaged] %s\n", p); } |
В | Copy Code |
---|---|
// MarshalString.cpp // compile with: /clr using namespace System; using namespace System::Runtime::InteropServices; value struct TraditionalDLL { [DllImport("TraditionalDLL2.dll")] static public void TakesAString([MarshalAs(UnmanagedType::LPStr)]String^); }; int main() { String^ s = gcnew String("sample string"); Console::WriteLine("[managed] passing managed string to unmanaged function..."); TraditionalDLL::TakesAString(s); Console::WriteLine("[managed] {0}", s); } |
This technique causes a copy of the string to be constructed on the unmanaged heap, so changes made to the string by the native function will not be reflected in the managed copy of the string. For passing strings by-reference, See How to: Modify System::String in Native Function Using PInvoke.
Note that no portion of the DLL is exposed to the managed code via the traditional #include directive. In fact, the DLL is accessed at runtime only, so problems with functions imported with DllImport will not be detected at compile time.