许可优化
许可优化
产品
产品
解决方案
解决方案
服务支持
服务支持
关于
关于
软件库
当前位置:服务支持 >  软件文章 >  Matlab arguments与Matlab.NET混合编程:直接调用Matlab内置函数

Matlab arguments与Matlab.NET混合编程:直接调用Matlab内置函数

阅读数 11
点赞 0
article_banner

1.传统的Matlab.NET混合编程步骤

传统的Matlab.NET混合编程有2种方式:

1)Matlab编写好M函数,利用deploytool编译m函数生成dll,在C#项目中引用并调用;

2)基于接口的编写方式,也是利用deploytool工具,过程繁琐一点,对编程人员素质要求高一点,但不需要进行繁琐的数据类型转换。

不管上面用哪种方式,Matlab和C#混编的基本步骤,大概都是下面的过程:

1) 编写M函数,并首先在Matlab中测试是正确可以调用的。注意命名规范,注释规范;

2) 使用命令打开 deploytool工具,设置项目名称,选择类型:.NET Assembly,然后新建一个类,并添加编写好的M函数

3) 编译,生成dll,并在C#项目中添加引用(还需要引用对应版本的MWArray),利用对象浏览器查看生成dll的方法结构,并根据Matlab和C#的类型转换规则,进行数据转换即可, 如果是接口的编程,这个过程相对要简单。

2.深入解析传统混编所生成的代码

2.1 第一步:编写M函数,并测试可以使用

为了好我们今天的目的相匹配,特意封装一个简单的内置函数,plot,来画一个简单的图形,如下所示M函数1 function PlotTest(n)

2 %编写一个简单的函数,对plot进行简单封装一下

3 plot(1:n,1:n);

4 %测试正确,才可以进行下一步工作

注意,混编必须是m函数function的形式才能被调用。上述函数简单测试一下,没有问题(复杂的函数一定要多测试,否则后续调试非常困难)。继续下一步。

2.2 第二步:在Matlab中使用deploytool建立混编项目

在Matlab工作区输入命令:deploytool,然后得到下面界面,输入混编项目的名称,选择存储位置,关键的是类型那里一定要选择".NET Assembly"。如下图所示:

选择“OK”之后,下一步matlab界面右侧会出现项目解决方案,需要添加类名称和M文件。这个类名称,就是编译完成之后C#项目中的类对象名称,然后添加我们刚才上一步编写的“PlotTest.m”,然后编译即可,如下图所示:

到此为止,一个常规 简单的Matlab.NET混编已经完成了60%了。编译完成之后,打开“Package”选项卡,即可看到生成的dll文件,然后点击右键,打开文件夹即可,如下图所示:

2.3 查看混编生成的代码

这个过程很关键,其实包含很多信息,只不过95%以上的人都没有注意到其实混编生成的dll是有源文件的,通过查看源文件就应该知道混编的原理,只不过这是matlab自动生成 而已。那看看生成的源码吧。

打开Matlab混编项目的目录,可以看到有2个文件夹,"distrib”,“src”2个文件夹。"distrib"文件夹就是上面图中生成的dll,注意有2个dll,1个是“项目名称.dll”,一个是“项目名称Native.dll”,这2个dll的差别可以通过"distrib"文件夹源码来观察。“distrib”就是源代码的文件夹。如下图所示,src文件夹的文件示意图:

我们2.2中新建的类名是TestDemo,所以生成的的源码名称也是TestDemo,看看这2个cs文件中的代码,同时类的方法也可以在VS中通过对象浏览器来查看dll有哪些方法以及方法的参数类型。直接贴这2个cs文件的代码,顺便解释和对比下:

TestDemo.cs文件源码:1 /*

2 * MATLAB Compiler: 4.17 (R2012a)

3 * Date: Mon Sep 09 16:19:01 2013

4 * Arguments: "-B" "macro_default" "-W" "dotnet:PlotTest,TestDemo,0.0,private" "-T"

5 * "link:lib" "-d" "https://www.gofarlic.com\Work\DevelopMent_SVN\Matlab\MatlabBlog\PlotTest\src" "-w"

6 * "enable:specified_file_mismatch" "-w" "enable:repeated_file" "-w"

7 * "enable:switch_ignored" "-w" "enable:missing_lib_sentinel" "-w" "enable:demo_license"

8 * "-v" "class{TestDemo:https://www.gofarlic.com\Work\DevelopMent_SVN\Matlab\MatlabBlog\PlotTest.m}"

9 */

10 using System;

11 using System.Reflection;

12 using System.IO;

13 using MathWorks.MATLAB.NET.Arrays;

14 using MathWorks.MATLAB.NET.Utility;

15

16 #if SHARED

17 [assembly: System.Reflection.AssemblyKeyFile(@"")]

18 #endif

19

20 namespace PlotTest

21 {

22

23   ///

24   /// The TestDemo class provides a CLS compliant, MWArray interface to the M-functions

25   /// contained in the files:

26   ///

27   /// https://www.gofarlic.com\Work\DevelopMent_SVN\Matlab\MatlabBlog\PlotTest.m

28   ///

29   /// deployprint.m

30   ///

31   /// printdlg.m

32   ///

33   ///

34   /// @Version 0.0

35   ///

36   public class TestDemo : IDisposable

37   {

38     #region Constructors

39

40     ///

41     /// The static constructor instantiates and initializes the MATLAB Compiler Runtime

42     /// instance.

43     ///

44     static TestDemo()

45     {

46       if (MWMCR.MCRAppInitialized)

47       {

48         Assembly assembly= Assembly.GetExecutingAssembly();

49

50         string ctfFilePath= assembly.Location;

51

52         int lastDelimiter= ctfFilePath.LastIndexOf(@"\");

53

54         ctfFilePath= ctfFilePath.Remove(lastDelimiter, (ctfFilePath.Length - lastDelimiter));

55

56         string ctfFileName = "PlotTest.ctf";

57

58         Stream embeddedCtfStream = null;

59

60         String[] resourceStrings = assembly.GetManifestResourceNames();

61

62         foreach (String name in resourceStrings)

63         {

64           if (name.Contains(ctfFileName))

65           {

66             embeddedCtfStream = assembly.GetManifestResourceStream(name);

67             break;

68           }

69         }

70         mcr= new MWMCR("",

71                        ctfFilePath, embeddedCtfStream, true);

72       }

73       else

74       {

75         throw new ApplicationException("MWArray assembly could not be initialized");

76       }

77     }

78

79

80     ///

81     /// Constructs a new instance of the TestDemo class.

82     ///

83     public TestDemo()

84     {

85     }

86

87

88     #endregion Constructors

89

90     #region Finalize

91

92     ///

93     /// Class destructor called by the CLR garbage collector.

94     ///

95     ~TestDemo()

96     {

97       Dispose(false);

98     }

99

100

101     ///

102     /// Frees the native resources associated with this object

103     ///

104     public void Dispose()

105     {

106       Dispose(true);

107

108       GC.SuppressFinalize(this);

109     }

110

111

112     ///

113     /// Internal dispose function

114     ///

115     protected virtual void Dispose(bool disposing)

116     {

117       if (!disposed)

118       {

119         disposed= true;

120

121         if (disposing)

122         {

123           // Free managed resources;

124         }

125

126         // Free native resources

127       }

128     }

129

130

131     #endregion Finalize

132

133     #region Methods

134

135     ///

136     /// Provides a void output, 0-input MWArrayinterface to the PlotTest M-function.

137     ///

138     ///

139     /// M-Documentation:

140     /// 编写一个简单的函数,对plot进行简单封装一下

141     ///

142     ///

143     public void PlotTest()

144     {

145       mcr.EvaluateFunction(0, "PlotTest", new MWArray[]{});

146     }

147

148

149     ///

150     /// Provides a void output, 1-input MWArrayinterface to the PlotTest M-function.

151     ///

152     ///

153     /// M-Documentation:

154     /// 编写一个简单的函数,对plot进行简单封装一下

155     ///

156     /// Input argument #1

157     ///

158     public void PlotTest(MWArray n)

159     {

160       mcr.EvaluateFunction(0, "PlotTest", n);

161     }

162

163

164     ///

165     /// Provides the standard 0-input MWArray interface to the PlotTest M-function.

166     ///

167     ///

168     /// M-Documentation:

169     /// 编写一个简单的函数,对plot进行简单封装一下

170     ///

171     /// The number of output arguments to return.

172     /// An Array of length "numArgsOut" containing the output

173     /// arguments.

174     ///

175     public MWArray[] PlotTest(int numArgsOut)

176     {

177       return mcr.EvaluateFunction(numArgsOut, "PlotTest", new MWArray[]{});

178     }

179

180

181     ///

182     /// Provides the standard 1-input MWArray interface to the PlotTest M-function.

183     ///

184     ///

185     /// M-Documentation:

186     /// 编写一个简单的函数,对plot进行简单封装一下

187     ///

188     /// The number of output arguments to return.

189     /// Input argument #1

190     /// An Array of length "numArgsOut" containing the output

191     /// arguments.

192     ///

193     public MWArray[] PlotTest(int numArgsOut, MWArray n)

194     {

195       return mcr.EvaluateFunction(numArgsOut, "PlotTest", n);

196     }

197

198

199

200     ///

201     /// This method will cause a MATLAB figure window to behave as a modal dialog box.

202     /// The method will not return until all the figure windows associated with this

203     /// component have been closed.

204     ///

205     ///

206     /// An application should only call this method when required to keep the

207     /// MATLAB figure window from disappearing.  Other techniques, such as calling

208     /// Console.ReadLine() from the application should be considered where

209     /// possible.

210     ///

211     public void WaitForFiguresToDie()

212     {

213       mcr.WaitForFiguresToDie();

214     }

215

216

217

218     #endregion Methods

219

220     #region Class Members

221

222     private static MWMCR mcr= null;

223

224     private bool disposed= false;

225

226     #endregion Class Members

227   }

228 }

TestDemoNative.cs文件源码:1 /*

2 * MATLAB Compiler: 4.17 (R2012a)

3 * Date: Mon Sep 09 16:19:01 2013

4 * Arguments: "-B" "macro_default" "-W" "dotnet:PlotTest,TestDemo,0.0,private" "-T"

5 * "link:lib" "-d" "https://www.gofarlic.com\Work\DevelopMent_SVN\Matlab\MatlabBlog\PlotTest\src" "-w"

6 * "enable:specified_file_mismatch" "-w" "enable:repeated_file" "-w"

7 * "enable:switch_ignored" "-w" "enable:missing_lib_sentinel" "-w" "enable:demo_license"

8 * "-v" "class{TestDemo:https://www.gofarlic.com\Work\DevelopMent_SVN\Matlab\MatlabBlog\PlotTest.m}"

9 */

10 using System;

11 using System.Reflection;

12 using System.IO;

13 using MathWorks.MATLAB.NET.Arrays;

14 using MathWorks.MATLAB.NET.Utility;

15

16 #if SHARED

17 [assembly: System.Reflection.AssemblyKeyFile(@"")]

18 #endif

19

20 namespace PlotTestNative

21 {

22

23   ///

24   /// The TestDemo class provides a CLS compliant, Object (native) interface to the

25   /// M-functions contained in the files:

26   ///

27   /// https://www.gofarlic.com\Work\DevelopMent_SVN\Matlab\MatlabBlog\PlotTest.m

28   ///

29   /// deployprint.m

30   ///

31   /// printdlg.m

32   ///

33   ///

34   /// @Version 0.0

35   ///

36   public class TestDemo : IDisposable

37   {

38     #region Constructors

39

40     ///

41     /// The static constructor instantiates and initializes the MATLAB Compiler Runtime

42     /// instance.

43     ///

44     static TestDemo()

45     {

46       if (MWMCR.MCRAppInitialized)

47       {

48         Assembly assembly= Assembly.GetExecutingAssembly();

49

50         string ctfFilePath= assembly.Location;

51

52         int lastDelimiter= ctfFilePath.LastIndexOf(@"\");

53

54         ctfFilePath= ctfFilePath.Remove(lastDelimiter, (ctfFilePath.Length - lastDelimiter));

55

56         string ctfFileName = "PlotTest.ctf";

57

58         Stream embeddedCtfStream = null;

59

60         String[] resourceStrings = assembly.GetManifestResourceNames();

61

62         foreach (String name in resourceStrings)

63         {

64           if (name.Contains(ctfFileName))

65           {

66             embeddedCtfStream = assembly.GetManifestResourceStream(name);

67             break;

68           }

69         }

70         mcr= new MWMCR("",

71                        ctfFilePath, embeddedCtfStream, true);

72       }

73       else

74       {

75         throw new ApplicationException("MWArray assembly could not be initialized");

76       }

77     }

78

79

80     ///

81     /// Constructs a new instance of the TestDemo class.

82     ///

83     public TestDemo()

84     {

85     }

86

87

88     #endregion Constructors

89

90     #region Finalize

91

92     ///

93     /// Class destructor called by the CLR garbage collector.

94     ///

95     ~TestDemo()

96     {

97       Dispose(false);

98     }

99

100

101     ///

102     /// Frees the native resources associated with this object

103     ///

104     public void Dispose()

105     {

106       Dispose(true);

107

108       GC.SuppressFinalize(this);

109     }

110

111

112     ///

113     /// Internal dispose function

114     ///

115     protected virtual void Dispose(bool disposing)

116     {

117       if (!disposed)

118       {

119         disposed= true;

120

121         if (disposing)

122         {

123           // Free managed resources;

124         }

125

126         // Free native resources

127       }

128     }

129

130

131     #endregion Finalize

132

133     #region Methods

134

135     ///

136     /// Provides a void output, 0-input Objectinterface to the PlotTest M-function.

137     ///

138     ///

139     /// M-Documentation:

140     /// 编写一个简单的函数,对plot进行简单封装一下

141     ///

142     ///

143     public void PlotTest()

144     {

145       mcr.EvaluateFunction(0, "PlotTest", new Object[]{});

146     }

147

148

149     ///

150     /// Provides a void output, 1-input Objectinterface to the PlotTest M-function.

151     ///

152     ///

153     /// M-Documentation:

154     /// 编写一个简单的函数,对plot进行简单封装一下

155     ///

156     /// Input argument #1

157     ///

158     public void PlotTest(Object n)

159     {

160       mcr.EvaluateFunction(0, "PlotTest", n);

161     }

162

163

164     ///

165     /// Provides the standard 0-input Object interface to the PlotTest M-function.

166     ///

167     ///

168     /// M-Documentation:

169     /// 编写一个简单的函数,对plot进行简单封装一下

170     ///

171     /// The number of output arguments to return.

172     /// An Array of length "numArgsOut" containing the output

173     /// arguments.

174     ///

175     public Object[] PlotTest(int numArgsOut)

176     {

177       return mcr.EvaluateFunction(numArgsOut, "PlotTest", new Object[]{});

178     }

179

180

181     ///

182     /// Provides the standard 1-input Object interface to the PlotTest M-function.

183     ///

184     ///

185     /// M-Documentation:

186     /// 编写一个简单的函数,对plot进行简单封装一下

187     ///

188     /// The number of output arguments to return.

189     /// Input argument #1

190     /// An Array of length "numArgsOut" containing the output

191     /// arguments.

192     ///

193     public Object[] PlotTest(int numArgsOut, Object n)

194     {

195       return mcr.EvaluateFunction(numArgsOut, "PlotTest", n);

196     }

197

198

199     ///

200     /// Provides an interface for the PlotTest function in which the input and output

201     /// arguments are specified as an array of Objects.

202     ///

203     ///

204     /// This method will allocate and return by reference the output argument

205     /// array.

206     /// M-Documentation:

207     /// 编写一个简单的函数,对plot进行简单封装一下

208     ///

209     /// The number of output arguments to return

210     /// Array of Object output arguments

211     /// Array of Object input arguments

212     /// Array of Object representing variable input

213     /// arguments

214     ///

215     [MATLABSignature("PlotTest", 1, 0, 0)]

216     protected void PlotTest(int numArgsOut, ref Object[] argsOut, Object[] argsIn, params Object[] varArgsIn)

217     {

218         mcr.EvaluateFunctionForTypeSafeCall("PlotTest", numArgsOut, ref argsOut, argsIn, varArgsIn);

219     }

220

221     ///

222     /// This method will cause a MATLAB figure window to behave as a modal dialog box.

223     /// The method will not return until all the figure windows associated with this

224     /// component have been closed.

225     ///

226     ///

227     /// An application should only call this method when required to keep the

228     /// MATLAB figure window from disappearing.  Other techniques, such as calling

229     /// Console.ReadLine() from the application should be considered where

230     /// possible.

231     ///

232     public void WaitForFiguresToDie()

233     {

234       mcr.WaitForFiguresToDie();

235     }

236

237

238

239     #endregion Methods

240

241     #region Class Members

242

243     private static MWMCR mcr= null;

244

245     private bool disposed= false;

246

247     #endregion Class Members

248   }

249 }

对比大家就可以发现,只不过一个更加傻瓜化,参数都是Object了,其实这样反而不好,增加了类型转换的代价,如果知道,为何不给一个正确的给他呢。关于这2个dll的速度,曾经听说过是有差别的,博客园有人给过测试,我没实际测试过,还是习惯用传统的TestDemo.cs,因为参数类型都是Object,不直观,出了问题也有点头疼。

2.4 上述Matlab自动生成代码的要点

后面某些类或者方法的XML注释就不说了,自动生成的东西,可以对照M文件的注释来看。

1.首先看第一段的注释信息:1 /*

2 * MATLAB Compiler: 4.17 (R2012a)

3 * Date: Mon Sep 09 16:19:01 2013

4 * Arguments: "-B" "macro_default" "-W" "dotnet:PlotTest,TestDemo,0.0,private" "-T"

5 * "link:lib" "-d" "https://www.gofarlic.com\Work\DevelopMent_SVN\Matlab\MatlabBlog\PlotTest\src" "-w"

6 * "enable:specified_file_mismatch" "-w" "enable:repeated_file" "-w"

7 * "enable:switch_ignored" "-w" "enable:missing_lib_sentinel" "-w" "enable:demo_license"

8 * "-v" "class{TestDemo:https://www.gofarlic.com\Work\DevelopMent_SVN\Matlab\MatlabBlog\PlotTest.m}"

9 */

10 using System;

11 using System.Reflection;

12 using System.IO;

13 using MathWorks.MATLAB.NET.Arrays;

14 using MathWorks.MATLAB.NET.Utility;

上面这段信息主要是说明当前Matlab编译器的版本,因为编译的版本和部署的MCR版本必须对应起来,否则是不能运行的。然后有编译日期,以及编译的参数,类名以及M函数的地址等信息,其实知道这些参数,理论上是可以自己在程序里面调用Matlab的编译器进行编译工作的,只不过比较复杂,能力有限,研究不下去。下面的引用大家应该明白,这个是MWArray.dll里面的命名空间,所以混编的项目都要引用对应版本的MWArray.dll

2.关键的静态构造函数1 static TestDemo()

2     {

3       if (MWMCR.MCRAppInitialized)

4       {

5         Assembly assembly= Assembly.GetExecutingAssembly();

6         string ctfFilePath= assembly.Location;

7         int lastDelimiter= ctfFilePath.LastIndexOf(@"\");

8         ctfFilePath= ctfFilePath.Remove(lastDelimiter, (ctfFilePath.Length - lastDelimiter));

9         string ctfFileName = "PlotTest.ctf";

10         Stream embeddedCtfStream = null;

11         String[] resourceStrings = assembly.GetManifestResourceNames();

12         foreach (String name in resourceStrings)

13         {

14           if (name.Contains(ctfFileName))

15           {

16             embeddedCtfStream = assembly.GetManifestResourceStream(name);

17             break;

18           }

19         }

20         mcr= new MWMCR("",ctfFilePath, embeddedCtfStream, true);

21       }

22       else

23       {

24         throw new ApplicationException("MWArray assembly could not be initialized");

25       }

26     }

如果有一些C#的开发和编程经验看上面的代码问题应该不大,否则还真有点难说清楚,简单的说几个要点:

1)这个构造函数的作用主要是检测MCR对象是否初始化,如果没有,则寻找程序集,并拼接资源的位置,正确进行初始化

2) 上面的ctf其实是包含在dll程序集里面的,以资源的形式,这个文件是核心,它才真正的包括了Matlab编译之后的,MCR可以运行的中间程序。

3) 必须要合法正确的ctf文件和dll文件才能 正确的初始化mcr对象,合法的意思,是Matlab内部有校验机制,包括对相关版本,在以前的版本生成文件中,很明显,现在2012a里面都隐藏起来了,不过要求都一样。

4) 上面方法其实已经间接的告诉了我们怎么初始化mcr对象,有了mcr对象,一切都好办了,因为它才是MCR的核心。

3.PlotTest封装的方法代码1     public void PlotTest()

2     {

3       mcr.EvaluateFunction(0, "PlotTest", new MWArray[]{});

4     }

5     public void PlotTest(MWArray n)

6     {

7       mcr.EvaluateFunction(0, "PlotTest", n);

8     }

9     public MWArray[] PlotTest(int numArgsOut)

10     {

11       return mcr.EvaluateFunction(numArgsOut, "PlotTest", new MWArray[]{});

12     }

13     public MWArray[] PlotTest(int numArgsOut, MWArray n)

14     {

15       return mcr.EvaluateFunction(numArgsOut, "PlotTest", n);

16     }

看了字段代码,再对应mcr的初始化,其实都很明朗了。通过mcr的EvaluateFunction来调用M函数。上面的代码有几个重载方法,可以实用很多不同的情况,有时候,这些方法的个数会更多,其实没多大必要,也可以自己编译一下,把没用的删掉,保留少数几个有用的即可。同时也可以看到,这里直接通过字符串来传递函数名称的,因此必须保证这个函数能被mcr搜索到。比如我们这里的"PlotTest"这个函数其实就包含了ctf文件中(注意ctf文件是可以和dll分开的,在混编项目里可以设置)。

3.上述代码到内置函数的调用

上述已经讲解了整个mcr调用的过程,其实就是通过mcr的EvaluateFunction来调用M函数,但要保证对于的函数名称在mcr搜索的范围内。那么我们是不是可以假设:内置函数都在MCR内部,应该是可以搜索到的,那么把上面的函数名称换一下,是不是也是可行的。这个假设也是我最早接触时候的想法,有了假设,当然要去验证。现在看来这个当然是肯定的,那么不妨重新演示一遍。过程不详细讲了,代码也有注释,混编要引用的MWArray.dll和命名空间也不提了,看代码:1 using System;

2 using System.Collections.Generic;

3 using System.Linq;

4 using System.Text;

5 using System.Reflection;

6 using System.IO;

7

8 using MathWorks.MATLAB.NET.Utility;

9 using MathWorks.MATLAB.NET.Arrays;

10

11

12 namespace BuildInFunctionDemo

13 {

14     class Program

15     {

16         static MWMCR mcr;

17         static void Main(string[] args)

18         {

19             #region 首先使用PlotTest.dll来初始化mcr,因为这个dll是混编“合法”产生的,只有这样才能顺利启动mcr

20             if (MWMCR.MCRAppInitialized)

21             {

22                 string path = Path.Combine(System.Environment.CurrentDirectory, "PlotTest.dll");

23                 Assembly assembly = Assembly.LoadFile(path);

24                 string ctfFilePath = assembly.Location;

25                 int lastDelimiter = ctfFilePath.LastIndexOf(@"\");

26                 ctfFilePath = ctfFilePath.Remove(lastDelimiter, (ctfFilePath.Length - lastDelimiter));

27                 string ctfFileName = "PlotTest.ctf";

28                 Stream embeddedCtfStream = null;

29                 String[] resourceStrings = assembly.GetManifestResourceNames();

30

31                 foreach (String name in resourceStrings)

32                 {

33                     if (name.Contains(ctfFileName))

34                     {

35                         embeddedCtfStream = assembly.GetManifestResourceStream(name);

36                         break;

37                     }

38                 }

39                 mcr = new MWMCR("",ctfFilePath, embeddedCtfStream, true);

40             }

41             else

42             {

43                 throw new ApplicationException("MWArray assembly could not be initialized");

44             }

45             #endregion

46

47             #region 直接调用混编dll中的封装函数进行测试

48             mcr.EvaluateFunction(0, "PlotTest", 5);

49

50             //注意这里要断点调试才能看到效果哦,因为mcr会把图绘制在一个Figure上面,

51             //后面的会覆盖前面的,这里暂停一下,可以看前面的效果

52             //下面就是直接调用matlab的plot函数的效果

53             MWNumericArray x = new double[]{1,2,3,4,5};

54             MWNumericArray y = new double[]{2,1,2.8,5.3,4.7};

55             mcr.EvaluateFunction(0, "plot",x,y );

56             #endregion

57

58             Console.ReadKey();

59         }

60     }

61 }

唯一要注意的就是50-52的说明,要加断点看2次绘制的效果。分别截图如下:


免责声明:本文系网络转载或改编,未找到原创作者,版权归原作者所有。如涉及版权,请联系删

相关文章
技术文档
QR Code
微信扫一扫,欢迎咨询~
customer

online

联系我们
武汉格发信息技术有限公司
湖北省武汉市经开区科技园西路6号103孵化器
电话:155-2731-8020 座机:027-59821821
邮件:tanzw@gofarlic.com
Copyright © 2023 Gofarsoft Co.,Ltd. 保留所有权利
遇到许可问题?该如何解决!?
评估许可证实际采购量? 
不清楚软件许可证使用数据? 
收到软件厂商律师函!?  
想要少购买点许可证,节省费用? 
收到软件厂商侵权通告!?  
有正版license,但许可证不够用,需要新购? 
联系方式 board-phone 155-2731-8020
close1
预留信息,一起解决您的问题
* 姓名:
* 手机:

* 公司名称:

姓名不为空

姓名不为空

姓名不为空
手机不正确

手机不正确

手机不正确
公司不为空

公司不为空

公司不为空