非托管C++通过C++/CLI包装调用C# DLL

项目中要给其它客户程序提供DLL做为接口,该项目是在.Net4.0平台下开发。终所周知.Net的各个版本之间存在着兼容性的问题,但是为了使用高版本运行平台的新特性,又不得不兼顾其它低版本平台客户程序的调用。为了解决这个问题尝试通过一个C++/CLI DLL对高版本的.Net DLL的接口加了一层包装,对外暴露C风格的接口给客户程序调用。

可支持的客户语言平台:

  • VB 6.0
  • VC++
  • .Net 1.0/.Net 1.1
  • .Net 2.0
  • .Net 3.5

创建C# .Net4.0的类库

  • 创建一个C#项目:Csharp

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Csharp
{
    public class CsharpClass
    {
        public int DoTesting(int x, int y, string testing, out string error)
        {
            error = testing + " -> testing is ok.";
            return x + y;
        }
    }
}

创建C++/CLI包装类库

  • 创建项目C++/CLI项目:CsharpWrap

  • 添加对Csharp的引用

  • CsharpWrap.h

// CsharpWrap.h

#pragma once

#include <windows.h>
#include <string> 

using namespace System;
using namespace Csharp;
using namespace std;
using namespace Runtime::InteropServices;
  • CsharpWrap.cpp

// This is the main DLL file.

#include "stdafx.h"

#include "CsharpWrap.h"

extern "C" _declspec(dllexport) int DoTesting(int x, int y, char* testing, char* error)
{
    try
    {
        CsharpClass ^generator = gcnew CsharpClass();
        String^ strTesting = gcnew String(testing);
        String^ strError;

        int sum = generator->DoTesting(x, y,strTesting, strError);

        if(strError != nullptr)
        {
            char* cError =
                (char*)(Marshal::StringToHGlobalAnsi(strError)).ToPointer();
            memcpy(error,cError,strlen(cError) + 1);
        }
        else
        {
            error = nullptr;
        }

        return sum;
    }
    catch(exception e)
    {
        memcpy(error,e.what(),strlen(e.what()) + 1);
        return -1;
    }
}
  • 项目输出和使用

  1. Csharp.dll
  2. CsharpWrap.dll

如果要调用CsharpWrap.dll必须保证Csharp.dll也被调用程序可见(即应该放在进程EXE文件同一目录下)

调用Demo代码

  • C++

HINSTANCE hInst= LoadLibrary(_T("CsharpWrap.dll"));
if(hInst)
{
    pfunc DoTesting = (pfunc)GetProcAddress(hInst,"DoTesting");

    if(DoTesting)
    {
        char error[100]= {NULL};
        int sum = DoTesting(1, 2, "Hello", error);
        //show testing results
        char strSum[8];
        _itoa_s(sum,strSum,16);
        ::MessageBoxA(NULL, error, strSum, MB_OK);
    }
    else
    {
        ::MessageBoxA(NULL, "Get function fail.", "Fail", MB_OK);
    }
    //free library
    FreeLibrary(hInst);
    hInst = nullptr;
}
else
{
    ::MessageBoxA(NULL, "Load dll fail.", "Fail", MB_OK);
}
  • C#低版本.Net

导出函数

[DllImport("CsharpWrap.dll")]static extern int DoTesting(int x, int y, string testing, StringBuilder error);

调用

StringBuilder sb = new StringBuilder(200);
int sum  = DoTesting(1, 2, "Hellow", sb);
MessageBox.Show(sb.ToString(), sum.ToString());
  • VB6.0

导出函数

Public Declare Function DoTesting Lib "CsharpWrap.dll" (ByVal x As Integer, ByVal y As Integer, ByVal testing As String, ByVal error As String) As Integer

调用

Dim error As String
Dim testing As String
Dim x As Integer
Dim y As Integer
Dim sum As Integer

testing = "Hello"
error = String(60000, vbNullChar)
sum = DoTesting(x, y, testing, error)

常见问题及注意事项

  • 平台工具集的问题

如果使用的是VS2010以上的版本编译出来的C++/CLI DLL可能会遇到缺少依赖等运行错误。

如果要支持XP系统工具集尽量用_xp结尾的

平台工具集的依赖DLL,把下面的或其它相应版本的依赖DLL放到目标机器的C:\WINDOWS\SYSTEM32下

      1. msvcp100.dll
      2. msvcr100.dll
      3. msvcp110.dll
      4. msvcr110.dll
  • 字符集的问题,因为.Net默认用的字符集是Unicode,但是客户程序有可能是其它字符集,这样也可能会造成字符串在程序间的兼容问题。

可选择Unicode/Multi-Byte,根据不项目需求选择相应的字符集

代码内对不同字符集进行转换

从char* to 宽字符

wchar_t *GetWC(const char *c)
{
    const size_t cSize = strlen(c)+1;
    wchar_t* wc = new wchar_t[cSize];
    MultiByteToWideChar(CP_ACP,0,(const char *)c,int(cSize),wc,int(cSize));
    return wc;
}

String^ to Char*

char* cError = (char*)(Marshal::StringToHGlobalAnsi(strError)).ToPointer();
  • 调用的C++/CLI DLL的时候传入参数的问题

C#调用的时候String参数对应的类型应该是StringBuilder,要注意StringBuilder的容量,默认是256个字符,如果返回的比较多的东西要注意初始化相应大小的容量。

  • DLL多层嵌套的问题

如果用LoadLibrary加载DLL失败,可以尝试用LoadLibraryEx,同时保证所依赖的C#DLL放到进程EXE同级目录。

LoadLibraryEx("DLL绝对路径", NULL, LOAD_WITH_ALTERED_SEARCH_PATH);

时间: 2024-10-10 17:42:57

非托管C++通过C++/CLI包装调用C# DLL的相关文章

[.net 面向对象程序设计进阶] (8) 托管与非托管

本节导读:虽然在.NET编程过程中,绝大多数内存垃圾回收由CLR(公共语言运行时)自动回收,但也有很多需要我们编码回收.掌握托管与非托管的基本知识,可以有效避免某些情况下导致的程序异常. 1.什么是托管与非托管? 托管资源:一般是指被CLR(公共语言运行时)控制的内存资源,这些资源由CLR来管理.可以认为是.net 类库中的资源. 非托管资源:不受CLR控制和管理的资源. 对于托管资源,GC负责垃圾回收.对于非托管资源,GC可以跟踪非托管资源的生存期,但是不知道如何释放它,这时候就要人工进行释放

C#内存管理之托管堆与非托管堆( reprint )

在 .NET Framework 中,内存中的资源(即所有二进制信息的集合)分为“托管资源”和“非托管资源”.托管资源必须接受 .NET Framework 的 CLR (通用语言运行时)的管理(诸如内存类型安全性检查),而非托管资源则不必接受 .NET Framework 的 CLR 管理.(了解更多区别请参阅 .NET Framework 或 C# 的高级编程资料)托管资源在 .NET Framework 中又分别存放在两种地方:“堆栈”和“托管堆”(以下简称“堆”):规则是,所有的值类型(

.net 资源释放(托管资源和非托管资源)

1.托管资源 像int.float.DateTime等都是托管资源:net中80%的资源都是托管资源: 托管资源的回收通过GC(垃圾回收器)自动释放分配给该对象的内存,但无法预测进行垃圾回收的时间,我们无法控制系统在什么时间回收资源. 2.非托管资源 像ApplicationContext,Brush,Component,ComponentDesigner,Container,Context,Cursor,FileStream,Font,Icon,Image,Matrix,Object,Odbc

说说非托管资源的回收

释放未托管的资源有两种方法 1.析构函数 2.实现System.IDisposable接口 一.析构函数 构造函数可以指定必须在创建类的实例时进行的某些操作,在垃圾收集器删除对象时,也可以调用析构函数.析构函数初看起来似乎是放置释放未托管资源.执行一般清理操作的代码的最佳地方.但是,事情并不是如此简单.由于垃圾回收器的运行规则决定了,不能在析构函数中放置需要在某一时刻运行的代码,如果对象占用了宝贵而重要的资源,应尽可能快地释放这些资源,此时就不能等待垃圾收集器来释放了. 实例 C# 代码   复

编写高质量代码改善C#程序的157个建议——建议50:在Dispose模式中应区别对待托管资源和非托管资源

建议50:在Dispose模式中应区别对待托管资源和非托管资源 真正资源释放代码的那个虚方法是带一个bool参数的,带这个参数,是因为我们在资源释放时要区别对待托管资源和非托管资源. 提供给调用者调用的显式释放资源的无参Dispose方法中,调用参数是true: public void Dispose() { //必须为true Dispose(true); //省略其他代码 } 这表明,这时候代码要同时处理托管资源和非托管资源. 在供垃圾回收器调用的隐式清理资源的终结器中,调用的是false:

.net非托管资源的回收

释放未托管的资源有两种方法 1.析构函数 2.实现System.IDisposable接口 一.析构函数 构造函数可以指定必须在创建类的实例时进行的某些操作,在垃圾收集器删除对象时,也可以调用析构函数.析构函数初看起来似乎是放置释放未托管资源.执行一般清理操作的代码的最佳地方.但是,事情并不是如此简单.由于垃圾回收器的运行规则决定了,不能在析构函数中放置需要在某一时刻运行的代码,如果对象占用了宝贵而重要的资源,应尽可能快地释放这些资源,此时就不能等待垃圾收集器来释放了. 实例 C# 代码   复

C# DllImport“调用导致堆栈不对称。原因可能是托管的 PInvoke 签名与非托管的目标签名不匹配。请检查 PInvoke 签名的调用约定和参数与非托管的目标签名是否匹配 ”

调用外部dll时,出现如下问题 C# DllImport“调用导致堆栈不对称.原因可能是托管的 PInvoke 签名与非托管的目标签名不匹配.请检查 PInvoke 签名的调用约定和参数与非托管的目标签名是否匹配 ” 后来经过仔细检查发现,误把vb中的longx型当成64位,实际上它相当于C#中的32位int型.

在VS2010上使用C#调用非托管C++生成的DLL文件

背景 在项目过程中,有时候你需要调用非C#编写的DLL文件,尤其在使用一些第三方通讯组件的时候,通过C#来开发应用软件时,就需要利用DllImport特性进行方法调用.本篇文章将引导你快速理解这个调用的过程. 步骤 1. 创建一个CSharpInvokeCPP的解决方案: 2. 创建一个C++的动态库项目: 3. 在应用程序设置中,选择“DLL”,其他按照默认选项: 最后点击完成,得到如图所示项目: 我们可以看到这里有一些文件,其中dllmain.cpp作为定义DLL应用程序的入口点,它的作用跟

c# 如何调用非托管函数 (转)

在目前的项目当中经常需要调用系统API,或者第三方的API,而这些API通常都不是基于.NET的,也就是所说的非托管函数,还好.NET为我们提供了平台调用服务,通过这个服务,就可以轻松的实现我们的需求. 调用过程其实比较简单,主要分以下几个步骤: 1) 找到函数的定义以及他所在的链接库(DLL文件) 以系统提供的BEEP函数为例(用指定的频率和时间发出蜂鸣声),他就是在动态链接库kernel32.dll中定义的. 在MSDN上可以找到他的函数签名为 BOOL Beep(DWORD dwFreq,