【问题标题】:How to communicate from pure C (.c files) SDK to IOS (obj-C, .m files) framework?如何从纯 C(.c 文件)SDK 到 IOS(obj-C,.m 文件)框架进行通信?
【发布时间】:2016-07-20 19:43:29
【问题描述】:

前言:我对 iOS 中的 C 集成几乎没有经验,如有任何误解,请随时纠正我。

我有一个项目,它有一个用 C 编写的自定义 2 层“SDK”。coreSDK 对与 通信的 deviceSDK 进行方法调用>ios 框架 执行硬件操作(即启用相机)或检索设备信息(即 NSDocumentDirectory 的路径)。

很遗憾,由于基础架构的原因,SDK 文件扩展名都使用 (.h) 和 (.c),并且无法按照某些来源的建议更改为 (.m)。根据我的阅读,我可以为 ObjC 方法创建 C 回调,但这仅适用于单例,并且 C 回调需要在 (.m) 文件的范围内。

我尝试在 ObjC 中创建一个包装类,使其具有 deviceSDK 从中调用的 C 回调,但是当包含该 (.m) 的标头 (.h) 时,编译器似乎崩溃了。如果我的理解是正确的,那是因为 C 编译器无法解释 (.m) 中包含的 ObjC 语言。

我相信理论上可以使用 ObjC 运行时用纯 C 语言编写 iOS 应用程序,但最好不要走这条路,因为我所看到的复杂得荒谬。

工作流程示例

  1. ViewController.m 方法调用 coreSDK 方法 (.c)
  2. coreSDK 方法调用 deviceSDK 方法 (.c)
  3. deviceSDK 需要检索(例如)NSDocumentDirectory(或启用相机)

我怎样才能做到这一点?
代码示例将是最全面和最受赞赏的。
谢谢!


这些是我已经研究过的一些(不是全部)参考资料...


编辑:2016-07-21

为了澄清这个问题,我无法从 (.c) 文件中的 C 方法调用 (.m) 文件中的 ObjC 方法,或者想出一种替代方法来检索信息,例如(例如)NSDocumentsDirectory在 ObjC(.m 文件)中并在 (.c) 文件中传递回 C。

示例代码:当然它是不正确的,但它符合我的期望或我希望实现的目标。

//GPS.h
#include "GPSWrapper.h"
STATUS_Code GPSInitialize(void);
//GPS.c
#include "GPS.h"
STATUS_Code GPSInitialize(void) {
  GPS_cCallback();
}
//GPSWrapper.h
#import <Foundation/Foundation.h>
@interface GPSWrapper: NSObject
- (void) testObjC;
@end
//GPSWrapper.m
#import "GPSWrapper.h"
static id refToSelf = nil;
@implementation GPSWrapper
  - (void) testObjC {
    // just an example of a ObjC method call
    NSString *documentsDirectory = [paths objectAtIndex:0];
    NSLog(documentsDirectory);
  }
@end

static void GPS_cCallback() {
  [refToSelf testObjC];
}

【问题讨论】:

  • OSX 和 iOS 音频程序充满了指导性示例:虽然整个程序都是用 Obj-C 编写的,但关键的音频处理代码必须“精简”为纯 C。请查看 SO 标签“core-audio”。
  • 这真的没有意义; ObjC 可以 100% 使用 C。是的,这是单向的:你不能在编译为 C 的文件中#include 一个 ObjC 标头,或者发送 ObjC 消息,但是在任何一个中为你的 must-be-C 函数提供包装器都应该没有问题ObjC 类或编译为 ObjC 的文件中的 C 函数。您能否提供一个关于您正在做什么的最小演示,并准确解释它是如何失败的?
  • 你可以从Objective-C调用C方法,没什么大不了的。不要重命名,只需包含 .c 和 .h 文件并从那里开始。你想多了。
  • 问题不在于从 ObjC 调用 C,而更多的是从 (.c) 文件中的 C 方法调用 (.m) 文件中的 ObjC。我用更多细节和示例测试代码或我的期望更新了我的问题。

标签: ios objective-c c iphone


【解决方案1】:

您的示例中的这一行 #include "GPSWrapper.h" 是问题所在。您不能在被编译为 C 的文件中包含具有 ObjC 语法的标头。任何包含在 C 代码中的 ObjC 端的接口都需要分解成其自己的仅包含有效 C 的标头。这是一个您需要的演示:

首先,C 语言的头文件和实现文件。

//  CUtils.h
//  OCFromC

#ifndef CUtils_h
#define CUtils_h

#include <stdio.h>

void doThatThingYouDo();

void doThatThingWithThisObject(const void * objRef);

#endif /* CUtils_h */

//  CUtils.c
//  OCFromC

// Proof that this is being compiled without ObjC
#ifdef __OBJC__
#error "Compile this as C, please."
#endif

#include "CUtils.h"
#include "StringCounterCInterface.h"

void doThatThingYouDo()
{
    printf("%zu\n", numBytesInUTF32("Calliope"));
}

void doThatThingWithThisObject(const void * objRef)
{
    size_t len = numBytesInUTF32WithRef(objRef, "Calliope");
    printf("%zu\n", len);
}

注意第二个函数;如果需要,您可以在 C 语言中传递对象引用,只要它隐藏在 void * 中即可。对于内存管理,还需要进行一些转换。更多内容如下。

这是我们将使用的令人兴奋的 ObjC 类:

//  StringCounter.h
//  OCFromC

#import <Foundation/Foundation.h>

@interface StringCounter : NSObject

- (size_t)lengthOfBytesInUTF32:(const char *)s;

@end

 //  StringCounter.m
 //  OCFromC

 #import "StringCounter.h"

 @implementation StringCounter

 - (size_t)lengthOfBytesInUTF32:(const char *)s
 {
     NSString * string = [NSString stringWithUTF8String:s];
     return [string lengthOfBytesUsingEncoding:NSUTF32StringEncoding];
 }

 @end

现在,这是重要的部分。这是一个声明 C 函数的头文件。标头本身不包含 ObjC,因此可以将其包含在 CUtils.c 中,如上所示。

//  StringCounterCInterface.h
//  OCFromC

#ifndef StringCounterCInterface_h
#define StringCounterCInterface_h

// Get size_t definition
#import <stddef.h>

size_t numBytesInUTF32(const char * s);
size_t numBytesInUTF32WithRef(const void * scRef, const char *s);

#endif /* StringCounterCInterface_h */

这是连接点。它的实现文件被编译为ObjC,它包含了刚刚声明的那些函数的定义。此文件导入StringCounter 的接口,因此函数可以使用该类中的方法:

//  StringCounterCInterface.m
//  OCFromC

#ifndef __OBJC__
#error "Must compile as ObjC"
#endif

#import "StringCounterCInterface.h"
#import "StringCounter.h"

size_t numBytesInUTF32(const char * s)
{
    StringCounter * sc = [StringCounter new];
    // Or, use some "default" object in static storage here
    return [sc lengthOfBytesInUTF32:s];
}

size_t numBytesInUTF32WithRef(const void * objRef, const char * s)
{
    StringCounter * sc = (__bridge_transfer StringCounter *)objRef;
    return [sc lengthOfBytesInUTF32:s];
}

现在,您可以在 main 或任何您喜欢的地方从 CUtils 中使用这些功能:

//  main.m
//  OCFromC

#import <Foundation/Foundation.h>
#import "StringCounter.h"
#import "CUtils.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {

        doThatThing();
        // Create an object reference
        StringCounter * sc = [StringCounter new];
        // Tell ARC that I know this is dangerous, trust me,
        // pass this reference along.
        const void * scRef = (__bridge_retained void *)sc;
        doThatThingWithThisObject(scRef);
    }
    return 0;
}

桥接演员,连同它在 StringCounterCInterface.m 中的对应物,让您可以将 ObjC 对象移动通过 ARC 无法进入的区域。它会在对象隐藏在 void * 之前增加对象的保留计数,因此在您将 __bridge_transfer 重新分配回 ObjC 区域之前,它不会被意外释放。

最后一点:如果您要在 C 中大量传递对象引用,您可以考虑使用 typedef void * StringCounterRef; 并适当地更改所有内容的签名。

【讨论】:

  • 这是对我问题的一个很好的回答。虽然有点相关的问题,我注意到这里我们返回 size_t 字节大小,但是在玩 a 时,我必须将 objc 中字符串的返回类型定义为 const char * 到 c (知道 c 字符串是字符数组)。我花了一些时间才弄清楚这一点,但是在这种情况下我将如何定义其他常见的数据类型?
  • 我选择了size_t,这样C端就不必得到NSUInteger的定义,也就是lengthOfBytesUsingEncoding:的实际返回类型。它们并不完全相同,但实际上,(AFAIK)它们在所有平台上的大小相同。对于整个项目,重要的是要注意您编写的任何有效 C 都可以作为 ObjC 有效。因此,在交叉接口中,您只需要确保您使用的是标准 C 类型。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-03-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多