Powerscript如何调用C语言?

99ANYc3cd6
预计阅读时长 22 分钟
位置: 首页 C语言 正文
  1. C 语言端:编写并编译 DLL
  2. PowerScript 端:声明并调用函数
  3. 数据类型映射
  4. 常见问题与最佳实践

第 1 步:C 语言端 - 编写并编译 DLL

你需要用 C 语言编写一个函数,并将其编译成一个 Windows 动态链接库(.dll 文件)。

powerscript调用c语言
(图片来源网络,侵删)

1 编写 C 源代码

创建一个 C 源文件,my_functions.c,这里的关键是使用 __declspec(dllexport) 来导出你希望 PowerBuilder 调用的函数,这告诉编译器,这个函数应该放在 DLL 的导出表中。

示例:my_functions.c

#include <windows.h> // 包含 Windows API 头文件,如果需要的话
// 使用 __declspec(dllexport) 导出函数
// __stdcall 是 PowerBuilder 默认的调用约定,非常重要!
int __declspec(dllexport) __stdcall AddIntegers(int a, int b) {
    return a + b;
}
// 导出一个接收字符串并返回字符串长度的函数
size_t __declspec(dllexport) __stdcall GetStringLength(const char* str) {
    if (str == NULL) {
        return 0;
    }
    return strlen(str);
}
// 导出一个更复杂的函数,演示结构体传递
// 假设我们有一个简单的结构体
typedef struct {
    int id;
    char name[50];
} User;
// 函数接收一个 User 结构体指针,并修改其 id
void __declspec(dllexport) __stdcall ModifyUser(User* pUser, int newId) {
    if (pUser != NULL) {
        pUser->id = newId;
    }
}

代码要点解释:

  • __declspec(dllexport):这是 Visual C++ 的特定关键字,用于导出函数,其他编译器(如 GCC)可能使用 __attribute__((dllexport))
  • __stdcall这是最重要的部分,PowerBuilder 默认使用 __stdcall 调用约定,这规定了函数参数的入栈顺序和由谁清理栈空间,PowerBuilder 和 DLL 中的函数调用约定不匹配,会导致程序崩溃或返回错误结果。
  • 函数名:编译器会对导出的函数名进行“修饰”(name mangling)。AddIntegers 可能会被修饰为 ?AddIntegers@@YGHHH@Z,为了简化调用,PowerBuilder 提供了两种方式来处理这个问题:
    1. 使用 extern "C":在 C++ 代码中,用 extern "C" 包裹你的函数,可以防止 C++ 编译器进行名称修饰,使其以 C 语言的方式导出(_AddIntegers@16)。
    2. 在 PowerBuilder 中指定别名:这是更推荐、更灵活的方式,我们将在后面介绍。

2 编译 DLL

使用 Visual Studio 的命令行工具或其他 C 编译器(如 MinGW)来编译。

powerscript调用c语言
(图片来源网络,侵删)

使用 Visual Studio 开发者命令提示工具:

  1. 打开 "x64 Native Tools Command Prompt for VS 2025" (或其他版本)。
  2. 使用 cl (编译器) 命令来编译:
# /LD 选项表示生成 DLL
cl /LD my_functions.c

执行成功后,你会得到几个文件:

  • my_functions.dll:你的动态链接库。
  • my_functions.lib:导入库,用于链接。
  • my_functions.exp:导出文件。

使用 MinGW (GCC for Windows):

# -shared 选项表示生成共享库(DLL)
# -o 指定输出文件名
gcc -shared -o my_functions.dll my_functions.c

你的 my_functions.dll 文件已经准备好了。

powerscript调用c语言
(图片来源网络,侵删)

第 2 步:PowerScript 端 - 声明并调用函数

在 PowerBuilder 中,你需要在某个对象的 Global External FunctionsLocal External Functions 声明区中声明你刚才创建的 DLL 函数。

1 声明外部函数

打开 PowerBuilder 的画笔(如 Window 或 Application 画笔),在 "Declare" -> "Global External Functions" 菜单下添加声明。

示例 PowerScript 声明:

// 全局外部函数声明
// SUBROUTINE 表示没有返回值,FUNCTION 表示有返回值
// LIBRARY 指定 DLL 文件名(可以不带 .dll 扩展名)
// ALIAS 指定 DLL 中的函数名,可以解决名称修饰问题,是最佳实践
// 声明 AddIntegers 函数
FUNCTION long AddIntegers(long a, long b) LIBRARY "my_functions" ALIAS FOR "AddIntegers"
// 声明 GetStringLength 函数
FUNCTION ulong GetStringLength(ref string str) LIBRARY "my_functions" ALIAS FOR "GetStringLength"
// 声明 ModifyUser 函数
// 我们需要先在 PowerBuilder 中定义一个与 C 中 User 结构体匹配的结构体

声明要点解释:

  • FUNCTION vs SUBROUTINE:根据 C 函数是否有返回值来选择。
  • LIBRARY "my_functions":告诉 PowerBuilder 在哪个 DLL 中查找函数,PowerBuilder 会在应用程序的路径、System32 等目录下查找这个 DLL。
  • ALIAS FOR "AddIntegers"强烈推荐使用,它直接指定了 DLL 中导出的函数名(即 C 源代码中写的函数名),避免了因编译器不同而产生的名称修饰问题,使代码更具可移植性。

2 在代码中调用

声明完成后,你就可以像调用普通 PowerScript 函数一样调用它了。

示例 PowerScript 代码(例如在按钮的 Clicked 事件中):

// 1. 调用 AddIntegers 函数
long ll_num1, ll_num2, ll_sum
ll_num1 = 100
ll_num2 = 200
// 直接调用
ll_sum = AddIntegers(ll_num1, ll_num2)
MessageBox("结果", string(ll_num1) + " + " + string(ll_num2) + " = " + string(ll_sum))
// 2. 调用 GetStringLength 函数
string ls_my_string
ulong lul_length
ls_my_string = "Hello, PowerBuilder!"
// 注意:C 语言中的字符串是 char*,PowerBuilder 的 string 是 Unicode。
// PowerBuilder 会自动进行转换。
lul_length = GetStringLength(ls_my_string)
MessageBox("字符串长度", "字符串 '" + ls_my_string + "' 的长度是: " + string(lul_length))
// 3. 调用 ModifyUser 函数(需要先定义结构体)
// 在 PowerBuilder 的 Structure 画笔中创建一个名为 s_user 的结构体
// s_user 结构体应包含两个成员:
// id: integer
// name: string (长度为 49,因为 C 中 char[50] 需要留一个给空字符)
s_user lstr_user
lstr_user.id = 101
lstr_user.name = "张三"
MessageBox("调用前", "用户 ID: " + string(lstr_user.id))
// 调用 C 函数来修改结构体
ModifyUser(lstr_user, 999)
MessageBox("调用后", "用户 ID: " + string(lstr_user.id))

第 3 步:数据类型映射

这是最容易出错的地方,PowerScript 和 C 语言的数据类型需要正确对应。

PowerScript 类型 C / C++ 类型 说明
long int 最常用的整数类型。
ulong unsigned int 无符号整数。
real float 单精度浮点数。
double double 双精度浮点数。
string char* PowerBuilder 的 string 是 Unicode (UTF-16),C 的是 ANSI (UTF-8 或本地编码),PowerBuilder 在调用时会自动进行转换,但需注意字符集问题。
boolean intbool C99 标准引入了 bool,但通常用 int (0 为 false, 非0 为 true) 兼容性更好。
blob void*char* 用于传递二进制数据。
Any 不直接支持 不能直接传递 Any 类型,需要将其转换为具体类型(如 Blob)再传递。
结构体 struct PowerBuilder 的 Structure 可以和 C 的 struct 一一对应,成员名和类型必须匹配。
引用传递 type* (指针) 在 PowerScript 中使用 ref 关键字,在 C 端使用指针。

第 4 步:常见问题与最佳实践

找不到 DLL 或函数 (DLL not found / Function not found)

  • DLL 路径问题:确保 my_functions.dll 在以下位置之一:
    • 你的 PowerBuilder 应用程序的可执行文件(.exe)所在的目录。
    • Windows\System32 目录。
    • 添加到系统的 PATH 环境变量中。
    • LIBRARY 子句中提供完整路径:LIBRARY "C:\MyDlls\my_functions.dll"
  • 函数名问题:确保 ALIAS 子句中的名称与 DLL 中导出的名称完全一致,可以使用工具(如 Dependency Walker)来查看 DLL 中实际导出的函数名。

程序崩溃或 Access Violation

  • 调用约定不匹配:这是最致命的问题,确保 C 函数使用了 __stdcall,PowerBuilder 默认就是 __stdcall
  • 参数类型不匹配:C 函数期望一个 int,但你传了一个 string,仔细检查数据类型映射表。
  • 空指针:C 函数没有对传入的指针(如 char*struct*)进行 NULL 检查,而你传递了空值,确保在 PowerScript 中传递有效对象或使用 IsNull() 检查。
  • 栈损坏:参数个数不匹配,C 函数定义了3个参数,你在 PowerScript 中只传了2个。

字符集问题

如果你的 C 代码需要处理非英文字符,字符集转换可能会出现问题。

  • PowerBuilder 内部使用 Unicode (UTF-16)。
  • 默认情况下,PowerBuilder 在将 string 传递给 C 的 char* 时,会将其转换为 ANSI 编码(取决于系统区域设置)。
  • 如果你的 DLL 需要接收 UTF-8 字符串,你需要一个中间层:
    1. 在 PowerBuilder 中,使用 EncodingConvert() 函数将 Unicode string 转换为 UTF-8 Blob
    2. Blob 的内存地址(通过 BlobAddress() 函数获取)作为 void*char* 传递给 C 函数。
    3. 在 C 函数中,将 char* 作为 UTF-8 字符串处理。

最佳实践总结

  • 始终使用 ALIAS:避免因编译器差异导致的问题。
  • 坚持 __stdcall:在 C 代码中明确指定。
  • 参数校验:在 C 函数内部进行参数校验(如检查 NULL 指针),提高代码的健壮性。
  • 清晰的文档:为你的 DLL 编写文档,说明每个函数的用途、参数、返回值和可能需要的依赖项。
  • 错误处理:设计 C 函数时,可以通过返回特定的错误码(如 -1 或 0)来表示失败,让 PowerScript 端可以判断操作是否成功。
  • 使用 Blob 传递复杂数据:对于大块二进制数据或不确定结构的数据,使用 Blob 类型比 string 更安全。

通过以上步骤,你就可以成功地在 PowerScript 中调用强大的 C 语言功能了。

-- 展开阅读全文 --
头像
织梦qrcode.php如何使用?
« 上一篇 2025-12-16
dede友情链接网站简况如何调用?
下一篇 » 2025-12-16

相关文章

取消
微信二维码
支付宝二维码

目录[+]