Managed C# Byte Arrays to Un-Managed C++ Byte Arrays
Posted in software by Christopher R. Wirz on Sat Jul 08 2017
Bytes are very foundational in C#, whether streams, image files, or cryptography. Sometimes, you want to re-use unmanaged code (it can compile almost anywhere), but don't want to write it all in C#, just pass the bytes. Fortunately, passing byte arrays can be done!
- An unmanaged C++ lib
- A managed C++ DLL
- A managed C# executable
But it could also be done in two files:
- A managed C++ DLL
- A managed C# executable
If possible, compile the native C++ into a lib file so it can be directly embedded in an application or managed C++ DLL.
// "NativeClass.h"
using namespace std;
namespace NativeCode
{
class NativeClass
{
public:
NativeClass(std::vector<unsigned int> items);
int length();
unsigned int operator[] (int index);
unsigned char* GetBytes();
private:
std::vector<unsigned int> items;
};
}
// "NativeClass.cpp"
#include "NativeClass.h"
namespace NativeCode
{
NativeClass::NativeClass(std::vector<unsigned int> items) {
this->items = items;
}
int NativeClass::length() {
return (int)(this->items.size());
}
unsigned int NativeClass::operator[] (int index) {
if (index < 0) {
return 0;
}
if (this->length() <= 0) {
return 0;
}
if (index >= this->length()) {
return 0;
}
return this->items[index];
}
unsigned char* NativeClass::GetBytes() {
unsigned char* ret = new unsigned char[this->length()];
// item-wise, to prove a point
for (int n = 0; n < this->length(); n++) {
ret[n] = (*this)[n];
}
return ret;
}
}
Once the lib file is generated, link it to a manged C++ DLL. Managed DLLs are made using the compiler flag /cli.
The thing to note is that you can't pass managed objects into unmanaged memory in most cases. So, to be safe, copies must be made. The managed C++ class holds a pointer to the unmanaged class - it is just a wrapper and has no real logic; only data copying.
// "ManagedClass.h"
#include "../NativeCode/NativeClass.h"
namespace ManagedCode
{
public ref class ManagedClass
{
public:
ManagedClass(cli::array<System::Byte>^ bytes);
property int Length {
int get();
}
unsigned int operator[] (int index);
cli::array<System::Byte>^ GetBytes();
// Dispose method will make IDisposable
~ManagedClass();
private:
NativeCode::NativeClass* _nativePtr;
};
}
// "ManagedClass.cpp"
#include "stdafx.h"
#include <string>
#include <msclr\marshal_cppstd.h>
#include "ManagedClass.h"
namespace ManagedCode
{
ManagedClass::ManagedClass(cli::array<ystem::Byte>^ bytes) {
pin_ptr<System::Byte> p = &bytes[0];
unsigned char* uchars = p;
char* chars = reinterpret_cast<char*>(uchars);
int sz = strlen(chars);
std::vector<unsigned int> items = std::vector<unsigned int>(sz);
for (int n = sz - 1; n >= 0; n--) {
items[n] = (unsigned int)chars[n];
}
_nativePtr = new NativeCode::NativeClass(items);
}
int ManagedClass::Length::get()
{
return _nativePtr->length();
}
unsigned int ManagedClass::operator[](unsigned int index)
{
return (*_nativePtr)[index];
}
cli::array<System::Byte>^ ManagedClass::GetBytes() {
unsigned char* buf = _nativePtr->GetBytes();
int len = _nativePtr->length();
cli::array<System::Byte>^ byteArray = gcnew cli::array<System::Byte>(len);
System::Runtime::InteropServices::Marshal::Copy((IntPtr)buf, byteArray, 0, len);
delete buf;
return byteArray;
}
ManagedClass::~ManagedClass() { delete _nativePtr; }
}
Since the managed C++ DLL is done, it can be referenced from a C# project easily (making certain of x64 vs x86 compatibility).
There is one little trick to do at the end if you want subsetting. The trick is to create a child class.
// "DotNetClass.cs"
namespace MyApplication
{
public class DotNetClass : ManagedCode.ManagedClass
{
public DotNetClass(byte[] bytes) : base(bytes){}
// Since subscript indexing does not translate, this must be done
public uint this[int key] => base.op_Subscript(key);
}
}
Now byte arrays can pass between managed and unmanaged code.
return char array c++ dll to c#
你的问题似乎是如何在 C++ DLL 中返回一个字符数组,并且如何在 C# 中调用这个 DLL。下面是针对这个问题的回答。
首先,要在 C++ DLL 中返回一个字符数组,可以使用以下方法:
// 定义一个函数,返回 char* 类型的指针
extern "C" __declspec(dllexport) char* myFunc()
{
// 定义一个 char 数组,存放要返回的字符串
char myString[] = "Hello World";
// 在函数内部动态分配内存,以便返回 char* 指针
char* strPtr = new char[strlen(myString) + 1];
strcpy_s(strPtr, strlen(myString) + 1, myString);
// 返回 char* 指针
return strPtr;
}
在上述代码中,我们先定义了一个 char 数组 myString
,它存放了要返回的字符串。然后我们在函数内部动态分配了一块内存,将 myString
中的字符串复制到这个内存块中,并返回指向该内存块的指针。
接下来,我们可以在 C# 中调用这个 C++ DLL,如下所示:
[DllImport("myDll.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr myFunc();
// 在某个方法中调用 myFunc
IntPtr strPtr = myFunc();
string myString = Marshal.PtrToStringAnsi(strPtr);
在上述代码中,我们首先声明了一个名为 myFunc
的函数,它使用 DllImport
特性指定了要调用的 DLL 名称,以及调用约定。在调用 myFunc
函数时,我们可以得到返回的 char*
指针,并使用 Marshal.PtrToStringAnsi
方法将其转换为 C# 中的字符串。
需要注意的是,在 C++ 中使用 new
进行内存分配时,必须使用相应的 delete
释放内存。在 C# 中使用 IntPtr
获取到的指针,在使用完毕后也需要调用 Marshal.FreeHGlobal
释放内存。
首先,这是C++ DLL的示例代码:
extern "C" __declspec(dllexport) char* GetCharArray() { | |
const char* arr = "Hello, World!"; | |
char* copy = new char[strlen(arr) + 1]; | |
strcpy(copy, arr); | |
return copy; | |
} |
这段代码定义了一个函数GetCharArray
,它返回一个字符数组。这个数组是通过复制字符串"Hello, World!"
创建的。
然后,这是如何在C#中使用P/Invoke调用这个函数:
using System; | |
using System.Runtime.InteropServices; | |
class Program { | |
[ | |
private static extern IntPtr GetCharArray(); | |
static void Main() { | |
IntPtr ptr = GetCharArray(); | |
string result = Marshal.PtrToStringAnsi(ptr); | |
Console.WriteLine(result); // 输出 "Hello, World!" | |
} | |
} |
这段C#代码中,DllImport
属性用于导入DLL,CallingConvention.Cdecl
指定调用约定,Marshal.PtrToStringAnsi
用于将指针转换为字符串。注意,你需要将YourDllName
替换为你的DLL的实际名称。
请注意,此代码没有处理内存管理问题。在C++ DLL中,我们使用new
创建了一个字符数组,这意味着在C#中调用这个函数后,我们需要手动释放这块内存。在C#中,你可以使用Marshal.FreeString
来释放这块内存。但在实践中,通常建议在C++ DLL中使用智能指针或类似机制来自动管理内存,以避免内存泄漏问题。
new
分配的内存应该使用delete
来释放,而不是在C#中用Marshal.FreeString
。使用Marshal.FreeString
只会释放由C#字符串分配的内存,并不能释放C++中new
分配的内存。因此,你应该在C++代码中包含适当的清理代码,以确保正确地释放内存。C++ DLL (MyDll.cpp):
extern "C" __declspec(dllexport) std::string GetCharArray() { | |
std::string arr = "Hello, World!"; | |
char* copy = new char[arr.length() + 1]; | |
strcpy(copy, arr.c_str()); | |
return copy; | |
} | |
extern "C" __declspec(dllexport) void FreeCharArray(char* ptr) { | |
delete[] ptr; | |
} |
C# 应用程序 (Program.cs):
using System; | |
using System.Runtime.InteropServices; | |
class Program { | |
[ | |
private static extern string GetCharArray(); | |
[ | |
private static extern void FreeCharArray(string ptr); | |
static void Main() { | |
string result = GetCharArray(); | |
Console.WriteLine(result); // 输出 "Hello, World!" | |
// 释放内存 | |
FreeCharArray(result); | |
} | |
} |
在这个例子中,C++ DLL中使用了new
来分配内存,并在FreeCharArray
函数中用delete[]
来释放内存。在C#应用程序中,我们通过P/Invoke调用了这两个函数,并在使用完字符串后,通过FreeCharArray
释放了内存。这样就可以确保内存的正确管理和释放。
南来地,北往的,上班的,下岗的,走过路过不要错过!
======================个性签名=====================
之前认为Apple 的iOS 设计的要比 Android 稳定,我错了吗?
下载的许多客户端程序/游戏程序,经常会Crash,是程序写的不好(内存泄漏?刚启动也会吗?)还是iOS本身的不稳定!!!
如果在Android手机中可以简单联接到ddms,就可以查看系统log,很容易看到程序为什么出错,在iPhone中如何得知呢?试试Organizer吧,分析一下Device logs,也许有用.